Kottpd

October 29, 2025 ยท View on GitHub

Maven Central License: MIT Kotlin Build Coverage Branches

A lightweight REST framework written in pure Kotlin with zero external dependencies. Perfect for building microservices, REST APIs, and simple web servers.

Features

โœจ Pure Kotlin - No external framework dependencies
๐Ÿš€ Lightweight - Minimal footprint (~316 LOC)
๐Ÿ”’ HTTPS Support - Built-in SSL/TLS support
๐Ÿ›ฃ๏ธ Flexible Routing - Path-based and regex routing
๐ŸŽฏ Filters - Before/after request filters
โšก Simple API - Easy to learn and use
๐Ÿ“ Static Files - Built-in static file serving
๐ŸŽญ Exception Handling - Custom exception handlers

Quick Start

Installation

Add Kottpd to your project using Maven:

<dependency>
    <groupId>com.github.gimlet2</groupId>
    <artifactId>kottpd</artifactId>
    <version>0.2.2</version>
</dependency>

Or Gradle:

implementation("com.github.gimlet2:kottpd:0.2.2")

Hello World

import com.github.gimlet2.kottpd.Server

fun main() {
    val server = Server() // Default port is 9000
    
    server.get("/hello") { _, _ -> 
        "Hello, World!" 
    }
    
    server.start()
    println("Server running at http://localhost:9000")
}

Visit http://localhost:9000/hello to see your server in action!

Usage Examples

Basic Routing

val server = Server(port = 8080)

// GET request
server.get("/users") { req, res ->
    res.send("[{\"id\": 1, \"name\": \"John\"}]")
}

// POST request
server.post("/users") { req, res ->
    val userData = req.content
    res.send("User created: $userData", Status.Created)
}

// PUT request
server.put("/users/:id") { req, res ->
    res.send("User ${req.url} updated")
}

// DELETE request
server.delete("/users/:id") { req, res ->
    res.send("User deleted", Status.NoContent)
}

server.start()

Regex-based Routing

server.get("/api/v.*/users") { req, res ->
    res.send("Matches /api/v1/users, /api/v2/users, etc.")
}

server.get("/files/.*\\.pdf") { req, res ->
    res.send("PDF file requested")
}

Request Filters (Middleware)

// Global before filter (runs before all requests)
server.before { req, res ->
    println("Incoming request: ${req.method} ${req.url}")
    res.send("Log: Request received\n")
}

// Path-specific before filter
server.before("/api/.*") { req, res ->
    // Authentication check
    val token = req.headers["Authorization"]
    if (token == null) {
        res.send("Unauthorized", Status.Unauthorized)
    }
}

// After filter
server.after("/.*") { req, res ->
    res.send("\n--- Request completed ---")
}

Exception Handling

server.get("/error") { req, res ->
    throw IllegalStateException("Something went wrong!")
}

server.exception(IllegalStateException::class) { req, res ->
    res.send("Error handled gracefully", Status.InternalServerError)
}

Static File Serving

// Serve static files from resources/public
server.staticFiles("/public")

// Now files are accessible:
// resources/public/index.html -> http://localhost:9000/index.html
// resources/public/css/style.css -> http://localhost:9000/css/style.css

HTTPS/TLS Support

server.start(
    port = 9443,
    secure = true,
    keyStoreFile = "./keystore.jks",
    password = "keystorePassword"
)

Working with Headers

server.get("/headers") { req, res ->
    val userAgent = req.headers["User-Agent"]
    val contentType = req.headers["Content-Type"]
    
    res.send("User-Agent: $userAgent")
}

Reading Request Body

server.post("/data") { req, res ->
    val body = req.content
    val contentLength = req.headers["Content-Length"]
    
    res.send("Received ${contentLength} bytes: $body", Status.Created)
}

Complete Example

fun main() {
    Server(9000).apply {
        // Static files
        staticFiles("/public")
        
        // Routes
        get("/") { _, _ -> "Welcome to Kottpd!" }
        get("/hello") { _, res -> res.send("Hello") }
        get("/user/.*") { req, res -> res.send("User path: ${req.url}") }
        
        // POST with body
        post("/data") { req, res -> 
            res.send(req.content, Status.Created) 
        }
        
        // Filters
        before("/hello") { _, res -> res.send("before\n") }
        after("/hello") { _, res -> res.send("\nafter") }
        
        // Global filter
        before { _, res -> res.send("[LOG] ") }
        
        // Exception handling
        get("/error") { _, _ -> throw IllegalStateException("Test error") }
        exception(IllegalStateException::class) { _, _ -> "Error handled" }
        
        start()
    }.also {
        println("Server started on http://localhost:9000")
        println("Try: http://localhost:9000/hello")
    }
}

Architecture

Kottpd uses a simple architecture:

  1. Server - Main class that handles routing and server lifecycle
  2. ClientThread - Processes individual HTTP requests in separate threads
  3. HttpRequest - Represents incoming HTTP requests
  4. HttpResponse - Represents outgoing HTTP responses
  5. Status - HTTP status codes
  6. HttpMethod - Supported HTTP methods

Configuration

Custom Port

val server = Server(port = 8080)
// or via system property
// -Dserver.port=8080

Thread Pool

The server uses a cached thread pool by default, which creates new threads as needed and reuses previously constructed threads when available.

Documentation

Comparison with Other Frameworks

FeatureKottpdKtorJavalinSpring Boot
SizeTinyMediumSmallLarge
DependenciesNoneManyFewMany
Learning CurveEasyMediumEasySteep
PerformanceGoodExcellentGoodGood
Best ForSimple APIsProduction appsREST APIsEnterprise

Limitations

โš ๏ธ Current limitations to be aware of:

  • No built-in JSON serialization (bring your own library)
  • Basic error handling
  • Limited documentation
  • No built-in CORS support
  • Early development stage (v0.2.x)

See ROADMAP.md for planned improvements.

Contributing

We welcome contributions! Please see CONTRIBUTING.md for details.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Author

Andrei Chernyshev - @gimlet2

Support


Made with โค๏ธ using Kotlin