Ktor (server)
Ktor is JetBrains async, multi-platform web framework: server engine + a typed HTTP client + plugin system. Built on coroutines, runs on Netty/CIO/Jetty, scales like a Node app would but with Kotlin types. Use it when you want a server you can also share types with from the Android client.
Ktor server with routing, validation, and auth
EXAMPLE
// build.gradle.kts
// dependencies {
// implementation("io.ktor:ktor-server-core:2.3.12")
// implementation("io.ktor:ktor-server-netty:2.3.12")
// implementation("io.ktor:ktor-server-content-negotiation:2.3.12")
// implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
// implementation("io.ktor:ktor-server-auth:2.3.12")
// implementation("io.ktor:ktor-server-auth-jwt:2.3.12")
// implementation("io.ktor:ktor-server-status-pages:2.3.12")
// implementation("io.ktor:ktor-server-cors:2.3.12")
// implementation("io.ktor:ktor-server-call-logging:2.3.12")
// }
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.callloging.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.plugins.cors.routing.*
import io.ktor.server.plugins.statuspages.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.Serializable
@Serializable
data class CreateOrder(val customer: String, val totalCents: Long)
@Serializable
data class Order(val id: String, val customer: String, val totalCents: Long, val status: String)
@Serializable
data class ErrorBody(val code: String, val message: String)
fun main() {
embeddedServer(Netty, port = 3000, host = "0.0.0.0") {
install(ContentNegotiation) { json() }
install(CallLogging)
install(CORS) {
allowHost("app.example.com", schemes = listOf("https"))
allowCredentials = true
}
install(StatusPages) {
exception<Throwable> { call, cause ->
call.application.environment.log.error(cause.message, cause)
call.respond(HttpStatusCode.InternalServerError, ErrorBody("internal", ""))
}
}
install(Authentication) {
jwt("auth-jwt") {
realm = "shop"
verifier(io.ktor.server.auth.jwt.makeJwkProvider("https://issuer.example.com"), "shop-api")
validate { credential ->
if (credential.payload.audience.contains("shop-api")) JWTPrincipal(credential.payload) else null
}
}
}
routing {
get("/healthz") { call.respondText("ok") }
authenticate("auth-jwt") {
route("/api/orders") {
val store = mutableMapOf<String, Order>()
post {
val body = call.receive<CreateOrder>()
if (body.customer.isBlank() || body.totalCents < 0) {
call.respond(HttpStatusCode.BadRequest, ErrorBody("validation", "bad fields"))
return@post
}
val id = java.util.UUID.randomUUID().toString()
val order = Order(id, body.customer, body.totalCents, "new")
store[id] = order
call.response.headers.append(HttpHeaders.Location, "/api/orders/$id")
call.respond(HttpStatusCode.Created, order)
}
get("/{id}") {
val id = call.parameters["id"] ?: return@get call.respond(HttpStatusCode.BadRequest)
val order = store[id] ?: return@get call.respond(HttpStatusCode.NotFound)
call.respond(order)
}
}
}
}
}.start(wait = true)
}
// ===== Typed client (Ktor client) for unit + integration tests =====
// suspend fun createOrder(client: HttpClient, body: CreateOrder): Order =
// client.post("/api/orders") {
// contentType(ContentType.Application.Json); setBody(body)
// }.body()
//
// The SAME data classes are reused on Android (or another Ktor server),
// guaranteeing wire compatibility at compile time.
Why it matters
Share serializable data classes between your Ktor server and your Kotlin client/Android app. The wire format stops being a contract you maintain in two places — it is the same class, the compiler verifies both sides, and renaming a field is a refactor instead of a coordination event between two teams.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// build.gradle.kts: implementation("io.ktor:ktor-server-netty:…")
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.response.*
fun main() {
embeddedServer(Netty, port = 8080) {
routing { get("/") { call.respondText("hello") } }
}.start(wait = true)
}
Try it Yourself »
Discussion
Loading…