Ktor Server Core library providing foundational infrastructure for building asynchronous web applications and REST APIs with Kotlin
—
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.
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
}interface Route : ApplicationCallPipeline {
val parent: Route?
val selector: RouteSelector
// Route building functions
fun createChild(selector: RouteSelector): Route
fun handle(handler: PipelineInterceptor<Unit, PipelineCall>)
}// 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/*
}
}// 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 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): Routerouting {
// 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
}
}
}
}// 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")
}// 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
}
}
}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)
}// 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
}
}
}// 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
}
}
}// 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// 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)
}class RoutingPipelineCall(
call: ApplicationCall,
route: Route,
receivePipeline: ApplicationReceivePipeline,
sendPipeline: ApplicationSendPipeline,
parameters: Parameters
) : PipelineCall {
val route: Route
// Additional routing-specific context
}class RoutingResolveContext(
val routing: RoutingNode,
val call: ApplicationCall,
val parameters: Parameters
) {
// Context for route resolution process
}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()
}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)
}
}
}
}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
)
}
}
}
}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()
}
}
}// 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/
}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)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