Ktor Server — Routing and Middleware
Build HTTP servers with Ktor: routing DSL, middleware plugins, content negotiation, and error handling.
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.application.*
import io.ktor.server.routing.*
import io.ktor.server.response.*
import io.ktor.server.request.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.http.*
import kotlinx.serialization.Serializable
@Serializable
data class TodoItem(val id: Int, val title: String, val done: Boolean = false)
@Serializable
data class CreateTodo(val title: String)
@Serializable
data class ApiError(val message: String, val code: Int)
// Custom exception
class NotFoundException(message: String) : RuntimeException(message)
class ValidationException(message: String) : RuntimeException(message)
fun Application.configurePlugins() {
// JSON serialization
install(ContentNegotiation) {
json(kotlinx.serialization.json.Json {
prettyPrint = true
ignoreUnknownKeys = true
})
}
// Error handling
install(StatusPages) {
exception<NotFoundException> { call, cause ->
call.respond(HttpStatusCode.NotFound, ApiError(cause.message ?: "Not found", 404))
}
exception<ValidationException> { call, cause ->
call.respond(HttpStatusCode.BadRequest, ApiError(cause.message ?: "Invalid", 400))
}
exception<Throwable> { call, cause ->
call.respond(
HttpStatusCode.InternalServerError,
ApiError("Internal error: ${cause.message}", 500)
)
}
}
}
fun Application.configureRouting() {
val todos = mutableListOf(
TodoItem(1, "Learn Kotlin", true),
TodoItem(2, "Build Ktor app", false),
TodoItem(3, "Deploy to production", false)
)
var nextId = 4
routing {
// Health check
get("/health") {
call.respond(mapOf("status" to "ok", "timestamp" to System.currentTimeMillis()))
}
// REST API routes
route("/api/todos") {
// GET /api/todos
get {
val done = call.request.queryParameters["done"]?.toBoolean()
val filtered = if (done != null) todos.filter { it.done == done } else todos
call.respond(filtered)
}
// GET /api/todos/{id}
get("/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
?: throw ValidationException("Invalid ID")
val todo = todos.find { it.id == id }
?: throw NotFoundException("Todo $id not found")
call.respond(todo)
}
// POST /api/todos
post {
val body = call.receive<CreateTodo>()
if (body.title.isBlank()) throw ValidationException("Title required")
val todo = TodoItem(nextId++, body.title)
todos.add(todo)
call.respond(HttpStatusCode.Created, todo)
}
// PUT /api/todos/{id}/toggle
put("/{id}/toggle") {
val id = call.parameters["id"]?.toIntOrNull()
?: throw ValidationException("Invalid ID")
val index = todos.indexOfFirst { it.id == id }
if (index == -1) throw NotFoundException("Todo $id not found")
todos[index] = todos[index].copy(done = !todos[index].done)
call.respond(todos[index])
}
// DELETE /api/todos/{id}
delete("/{id}") {
val id = call.parameters["id"]?.toIntOrNull()
?: throw ValidationException("Invalid ID")
if (!todos.removeIf { it.id == id }) throw NotFoundException("Todo $id not found")
call.respond(HttpStatusCode.NoContent)
}
}
}
}
fun main() {
embeddedServer(Netty, port = 8080) {
configurePlugins()
configureRouting()
}.start(wait = true)
}Sponsored
Deploy on Railway
Use Cases
- REST API server with Ktor
- Structured error handling middleware
- Content negotiation and serialization
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
Ktor Server Routing and Middleware
Build HTTP APIs with Ktor: routing, content negotiation, authentication, and middleware plugins.
Best for: Building REST APIs with Kotlin-native framework
Ktor HTTP Client — GET, POST, and Auth
Make HTTP requests with Ktor client: GET, POST, headers, auth, retries, and JSON serialization.
Best for: REST API consumption in Kotlin applications
Null Safety — Elvis, Safe Call, and let
Master Kotlin null safety: safe calls, Elvis operator, let/also scoping, and smart casts.
Best for: Safe navigation through nullable chains
Data Classes — Copy, Destructure, and Equals
Use data classes for immutable models: auto-generated equals, hashCode, copy, and destructuring.
Best for: Immutable domain models and DTOs