CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-server-core

Ktor Server Core library providing foundational infrastructure for building asynchronous web applications and REST APIs with Kotlin

Pending
Overview
Eval results
Files

routing.mddocs/

Routing System

Ktor's routing system provides a flexible and powerful way to define URL patterns, handle HTTP methods, and organize application endpoints. The routing DSL allows you to create hierarchical route structures with parameter capturing, content negotiation, and middleware integration.

Core Routing Classes

RoutingNode

class RoutingNode(
    parent: RoutingNode?,
    selector: RouteSelector,
    developmentMode: Boolean,
    environment: ApplicationEnvironment
) : ApplicationCallPipeline, Route {
    val parent: RoutingNode?
    val selector: RouteSelector
    val children: List<RoutingNode>
    
    fun createChild(selector: RouteSelector): RoutingNode
    fun handle(body: PipelineContext<Unit, PipelineCall>.() -> Unit)
    operator fun invoke(body: Route.() -> Unit): Route
}

Route Interface

interface Route : ApplicationCallPipeline {
    val parent: Route?
    val selector: RouteSelector
    
    // Route building functions
    fun createChild(selector: RouteSelector): Route
    fun handle(handler: PipelineInterceptor<Unit, PipelineCall>)
}

Basic Route Creation

String Path Routing

// Route for exact string path
fun Route.route(path: String, build: Route.() -> Unit): Route

// Route for string path with specific HTTP method
fun Route.route(path: String, method: HttpMethod, build: Route.() -> Unit): Route

// Examples
routing {
    route("/users") {
        // Nested routes under /users
        route("/profile") {
            // Handles /users/profile
        }
    }
    
    route("/api", HttpMethod.Get) {
        // Only GET requests to /api/*
    }
}

Regex Path Routing

// Route for regex path pattern
fun Route.route(path: Regex, build: Route.() -> Unit): Route

// Example
routing {
    route(Regex("/files/.*\\.(jpg|png|gif)")) {
        // Matches image file paths
        handle {
            val path = call.request.path()
            call.respondText("Image file: $path")
        }
    }
}

HTTP Method Routes

Standard HTTP Methods

// HTTP method route functions
fun Route.get(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
fun Route.post(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route  
fun Route.put(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
fun Route.delete(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
fun Route.patch(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
fun Route.head(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
fun Route.options(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route

// Generic method route
fun Route.method(method: HttpMethod, body: Route.() -> Unit): Route

HTTP Method Examples

routing {
    // GET route
    get("/") {
        call.respondText("Welcome to Ktor!")
    }
    
    // POST route with path
    post("/users") {
        val user = call.receive<User>()
        val created = userService.create(user)
        call.respond(HttpStatusCode.Created, created)
    }
    
    // PUT route for updates
    put("/users/{id}") {
        val id = call.parameters["id"]!!
        val user = call.receive<User>()
        val updated = userService.update(id, user)
        call.respond(updated)
    }
    
    // DELETE route
    delete("/users/{id}") {
        val id = call.parameters["id"]!!
        userService.delete(id)
        call.respond(HttpStatusCode.NoContent)
    }
    
    // Custom method handling
    method(HttpMethod.Patch) {
        route("/users/{id}") {
            handle {
                // Handle PATCH request
            }
        }
    }
}

Parameter Routing

Path Parameters

// Route with parameter capture
get("/users/{id}") {
    val userId = call.parameters["id"]
    call.respondText("User ID: $userId")
}

// Multiple parameters
get("/users/{userId}/posts/{postId}") {
    val userId = call.parameters["userId"]
    val postId = call.parameters["postId"] 
    call.respond(getPost(userId, postId))
}

// Optional parameters with wildcards
get("/files/{path...}") {
    val filePath = call.parameters["path"]
    call.respondText("File path: $filePath")
}

Query Parameters

// Parameter routing functions
fun Route.param(name: String, value: String, build: Route.() -> Unit): Route
fun Route.param(name: String, build: Route.() -> Unit): Route
fun Route.optionalParam(name: String, build: Route.() -> Unit): Route

// Examples
routing {
    // Route only when parameter has specific value
    param("version", "v1") {
        get("/api/users") {
            // Only matches /api/users?version=v1
        }
    }
    
    // Route that captures any parameter value
    param("format") {
        get("/data") {
            val format = call.parameters["format"]
            // Matches /data?format=json, /data?format=xml, etc.
        }
    }
    
    // Route for optional parameter
    optionalParam("limit") {
        get("/items") {
            val limit = call.parameters["limit"]?.toIntOrNull() ?: 10
            // Works with or without ?limit=N
        }
    }
}

Parameter Access

get("/search") {
    // Query parameters
    val query = call.request.queryParameters["q"]
    val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1
    val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 20
    
    // Path parameters  
    val category = call.parameters["category"]
    
    val results = searchService.search(query, category, page, size)
    call.respond(results)
}

Header and Host Routing

Header-Based Routing

// Route for specific header value
fun Route.header(name: String, value: String, build: Route.() -> Unit): Route

// Examples
routing {
    header("Content-Type", "application/json") {
        post("/api/data") {
            // Only matches requests with JSON content type
            val data = call.receive<JsonObject>()
            call.respond(processJsonData(data))
        }
    }
    
    header("Accept", "application/xml") {
        get("/api/users") {
            // Only matches requests that accept XML
            val users = userService.getAllUsers()
            call.respond(users.toXml())
        }
    }
    
    header("Authorization") { // Any Authorization header
        get("/protected") {
            val token = call.request.header("Authorization")
            // Handle authenticated requests
        }
    }
}

Host and Port Routing

// Route for specific host
fun Route.host(host: String, build: Route.() -> Unit): Route

// Route for specific port
fun Route.port(port: Int, build: Route.() -> Unit): Route

// Examples
routing {
    host("api.example.com") {
        get("/v1/users") {
            // Only matches requests to api.example.com
        }
    }
    
    host("admin.example.com") {
        get("/dashboard") {
            // Admin interface on subdomain
        }
    }
    
    port(8443) {
        get("/secure") {
            // Only matches requests on port 8443
        }
    }
}

Route Selectors

RouteSelector Classes

// Base route selector interface
abstract class RouteSelector {
    abstract fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation
}

// Path segment selector
class PathSegmentRouteSelector(val value: String) : RouteSelector

// HTTP method selector  
class HttpMethodRouteSelector(val method: HttpMethod) : RouteSelector

// Parameter selector
class ParameterRouteSelector(val name: String) : RouteSelector

// HTTP header selector
class HttpHeaderRouteSelector(val name: String, val value: String? = null) : RouteSelector

Custom Route Selectors

// Create custom route selector
class CustomRouteSelector : RouteSelector() {
    override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
        // Custom routing logic
        return if (customCondition(context)) {
            RouteSelectorEvaluation.Constant
        } else {
            RouteSelectorEvaluation.Failed
        }
    }
    
    private fun customCondition(context: RoutingResolveContext): Boolean {
        // Implement custom routing condition
        return context.call.request.userAgent()?.contains("MyApp") == true
    }
}

// Use custom selector
fun Route.customRoute(build: Route.() -> Unit): Route {
    return createChild(CustomRouteSelector()).apply(build)
}

Routing Pipeline Classes

RoutingPipelineCall

class RoutingPipelineCall(
    call: ApplicationCall,
    route: Route,
    receivePipeline: ApplicationReceivePipeline,
    sendPipeline: ApplicationSendPipeline,
    parameters: Parameters
) : PipelineCall {
    val route: Route
    // Additional routing-specific context
}

RoutingResolveContext

class RoutingResolveContext(
    val routing: RoutingNode,
    val call: ApplicationCall,
    val parameters: Parameters
) {
    // Context for route resolution process
}

RoutingPath

class RoutingPath(val parts: List<RoutingPathSegment>) {
    companion object {
        fun parse(path: String): RoutingPath
        fun root(): RoutingPath
    }
}

sealed class RoutingPathSegment {
    object Root : RoutingPathSegment()
    data class Constant(val value: String) : RoutingPathSegment()
    data class Parameter(val name: String) : RoutingPathSegment()
}

Advanced Routing Patterns

Route Groups and Organization

routing {
    // API versioning
    route("/api") {
        route("/v1") {
            userRoutesV1()
            productRoutesV1()
        }
        
        route("/v2") {
            userRoutesV2()
            productRoutesV2()
        }
    }
    
    // Admin routes
    route("/admin") {
        authenticate("admin") {
            userManagementRoutes()
            systemRoutes()
        }
    }
    
    // Public routes
    staticRoutes()
    authRoutes()
}

fun Route.userRoutesV1() {
    route("/users") {
        get {
            call.respond(userService.getAllUsers())
        }
        
        post {
            val user = call.receive<User>()
            call.respond(userService.createUser(user))
        }
        
        route("/{id}") {
            get {
                val id = call.parameters["id"]!!
                call.respond(userService.getUser(id))
            }
            
            put {
                val id = call.parameters["id"]!!
                val user = call.receive<User>()
                call.respond(userService.updateUser(id, user))
            }
            
            delete {
                val id = call.parameters["id"]!!
                userService.deleteUser(id)
                call.respond(HttpStatusCode.NoContent)
            }
        }
    }
}

Content Negotiation in Routes

routing {
    route("/api/data") {
        // JSON endpoint
        header("Accept", "application/json") {
            get {
                call.respond(dataService.getData())
            }
        }
        
        // XML endpoint  
        header("Accept", "application/xml") {
            get {
                call.respondText(
                    dataService.getData().toXml(),
                    ContentType.Application.Xml
                )
            }
        }
        
        // CSV endpoint
        header("Accept", "text/csv") {
            get {
                call.respondText(
                    dataService.getData().toCsv(),
                    ContentType.Text.CSV
                )
            }
        }
    }
}

Conditional Routing

routing {
    // Development-only routes
    if (developmentMode) {
        route("/debug") {
            get("/routes") {
                val routes = collectRoutes(this@routing)
                call.respond(routes)
            }
            
            get("/config") {
                call.respond(application.environment.config.toMap())
            }
        }
    }
    
    // Feature-flagged routes
    if (featureFlags.isEnabled("new-api")) {
        route("/api/v3") {
            newApiRoutes()
        }
    }
}

Trailing Slash Configuration

IgnoreTrailingSlash

// Configure trailing slash handling
install(IgnoreTrailingSlash)

// Now these routes are equivalent:
// GET /users
// GET /users/

routing {
    get("/users") {
        call.respondText("Users endpoint")
    }
    // This will handle both /users and /users/
}

Route Information and Debugging

Route Collection

fun collectRoutes(root: Route): List<RouteInfo> {
    val routes = mutableListOf<RouteInfo>()
    
    fun collectRoutesRecursive(route: Route, path: String = "") {
        val currentPath = path + when (val selector = route.selector) {
            is PathSegmentRouteSelector -> "/${selector.value}"
            is ParameterRouteSelector -> "/{${selector.name}}"
            is HttpMethodRouteSelector -> " [${selector.method.value}]"
            else -> ""
        }
        
        if (route.children.isEmpty()) {
            routes.add(RouteInfo(currentPath, route.selector))
        }
        
        route.children.forEach { child ->
            collectRoutesRecursive(child, currentPath)
        }
    }
    
    collectRoutesRecursive(root)
    return routes
}

data class RouteInfo(val path: String, val selector: RouteSelector)

Complete Routing Example

fun Application.configureRouting() {
    routing {
        // Root endpoint
        get("/") {
            call.respondText("Welcome to our API")
        }
        
        // API routes
        route("/api/v1") {
            // Content-Type based routing
            header("Content-Type", "application/json") {
                
                // User management
                route("/users") {
                    get {
                        val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
                        val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 20
                        call.respond(userService.getUsers(page, size))
                    }
                    
                    post {
                        val user = call.receive<CreateUserRequest>()
                        val created = userService.createUser(user)
                        call.respond(HttpStatusCode.Created, created)
                    }
                    
                    route("/{id}") {
                        get {
                            val id = call.parameters["id"]!!
                            val user = userService.getUser(id)
                            if (user != null) {
                                call.respond(user)
                            } else {
                                call.respond(HttpStatusCode.NotFound)
                            }
                        }
                        
                        put {
                            val id = call.parameters["id"]!!
                            val updateRequest = call.receive<UpdateUserRequest>()
                            val updated = userService.updateUser(id, updateRequest)
                            call.respond(updated)
                        }
                        
                        delete {
                            val id = call.parameters["id"]!!
                            userService.deleteUser(id)
                            call.respond(HttpStatusCode.NoContent)
                        }
                        
                        // User posts
                        route("/posts") {
                            get {
                                val userId = call.parameters["id"]!!
                                call.respond(postService.getUserPosts(userId))
                            }
                            
                            post {
                                val userId = call.parameters["id"]!!
                                val post = call.receive<CreatePostRequest>()
                                val created = postService.createPost(userId, post)
                                call.respond(HttpStatusCode.Created, created)
                            }
                        }
                    }
                }
                
                // Search endpoint with query parameters
                get("/search") {
                    val query = call.request.queryParameters["q"]
                        ?: return@get call.respond(HttpStatusCode.BadRequest, "Missing query parameter")
                    val type = call.request.queryParameters["type"] ?: "all"
                    val results = searchService.search(query, type)
                    call.respond(results)
                }
            }
        }
        
        // Health check (no content-type requirement)
        get("/health") {
            call.respondText("OK", ContentType.Text.Plain)
        }
        
        // Static files
        route("/static/{path...}") {
            get {
                val path = call.parameters["path"]!!
                val file = File("static/$path")
                if (file.exists() && file.isFile) {
                    call.respondFile(file)
                } else {
                    call.respond(HttpStatusCode.NotFound)
                }
            }
        }
    }
}

This comprehensive routing documentation covers all aspects of Ktor's routing system, from basic URL matching to advanced patterns and conditional routing strategies.

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor-server-core

docs

application.md

configuration.md

engine.md

index.md

request-response.md

routing.md

tile.json