Routing adalah lapisan pertama yang menentukan bagaimana permintaan HTTP dipetakan ke handler. Ktor memakai DSL Kotlin untuk mendeklarasikan route, sehingga struktur route terbaca seperti kode biasa tanpa anotasi. Konsep utama yang harus dikuasai adalah route statis, route parameter, dan nested route. Pada chapter ini kita akan membedah ketiganya lewat model router yang dapat dijalankan tanpa dependensi eksternal.
DSL Routing Ktor #
DSL routing Ktor terlihat seperti berikut pada kode produksi:
routing {
get("/users") { call.respondText("daftar user") }
get("/users/{id}") { call.respondText("detail user ${call.parameters["id"]}") }
route("/admin") {
get("/stats") { call.respondText("stats") }
}
}Blok routing { } adalah receiver lambda yang membangun pohon route. Function get, post, put, delete mendaftarkan handler untuk HTTP method tertentu. Sintaks {id} menandai parameter dinamis yang diambil dari segmen path.
Route Statis #
Route statis cocokkan path secara persis. Route get("/users") hanya cocok dengan permintaan GET /users, bukan GET /users/1 atau GET /users/. Ktor tidak menambahkan slash otomatis, sehingga path harus konsisten antara deklarasi dan permintaan.
Route Parameter #
Parameter dinamis ditulis dengan sintaks {nama}. Saat Ktor menerima permintaan GET /users/42, route get("/users/{id}") cocok dan parameter id bernilai "42". Kode produksi membaca parameter via call.parameters["id"] yang mengembalikan String? — null jika parameter tidak ada.
Beberapa parameter dapat dirangkai: get("/users/{userId}/posts/{postId}") akan mengekstrak userId dan postId sekaligus. Validasi tipe (apakah id adalah angka) tetap tanggung jawab handler — Ktor tidak memvalidasi format otomatis.
Nested Routes #
Route dapat dikelompokkan dengan route("/prefix") { ... }. Semua route di dalam blok akan mendapatkan prefix tersebut. Nested route berguna untuk mengelompokkan endpoint dengan otorisasi atau domain yang sama.
Demo Router Ktor #
Agar konsep routing dapat dijalankan tanpa dependensi Ktor nyata, kita akan memodelkan router sebagai class Kotlin biasa. Model ini mendukung route statis, parameter dinamis, dan nested route — lengkap dengan algoritma pencocokan path.
// ktor_routing.kt
// Mendemonstrasikan routing Ktor: route parameter, route group,
// dan nested routes. Pada kode produksi, routing { } diproses oleh plugin
// Routing Ktor. Di sini kita memakai pendekatan DSL ringkas.
class Route(val path: String) {
val children = mutableListOf<Route>()
private var _handler: ((Map<String, String>) -> String)? = null
fun handle(block: (Map<String, String>) -> String) {
_handler = block
}
fun route(sub: String, build: Route.() -> Unit = {}) {
val child = Route(sub)
child.build()
children.add(child)
}
// {id} -> parameter dinamis
fun get(param: String, build: Route.() -> Unit) {
val child = Route(param)
child.build()
children.add(child)
}
// Mencocokkan path dan mengeksekusi handler
fun match(segments: List<String>, params: MutableMap<String, String>): String? {
if (segments.isEmpty()) {
return _handler?.invoke(params)
}
val first = segments.first()
val rest = segments.drop(1)
for (child in children) {
if (child.path.startsWith("{") && child.path.endsWith("}")) {
val key = child.path.removeSurrounding("{", "}")
params[key] = first
val result = child.match(rest, params)
if (result != null) return result
params.remove(key)
} else if (child.path == first) {
val result = child.match(rest, params)
if (result != null) return result
}
}
return null
}
}
class Routing {
val root = Route("")
fun route(path: String, build: Route.() -> Unit) {
root.route(path, build)
}
}
fun routing(build: Routing.() -> Unit): Routing {
val r = Routing()
r.build()
return r
}
fun main() {
val router = routing {
route("users") {
// GET /users -> daftar semua
handle { "GET /users -> daftar semua" }
// GET /users/{id} -> detail
get("{id}") {
handle { params -> "GET /users/${params["id"]} -> detail" }
// Nested route: GET /users/{id}/posts
route("posts") {
handle { params -> "GET /users/${params["id"]}/posts" }
}
}
}
route("health") {
handle { "OK" }
}
}
println("=== Route matching ===")
val paths = listOf("/users", "/users/42", "/users/42/posts", "/health", "/missing")
paths.forEach { p ->
val segments = p.split("/").filter { it.isNotEmpty() }
val params = mutableMapOf<String, String>()
val result = router.root.match(segments, params)
val paramStr = if (params.isNotEmpty()) " params=$params" else ""
println(" $p -> ${result ?: "404"}$paramStr")
}
}Output yang Dihasilkan:
=== Route matching ===
/users -> GET /users -> daftar semua
/users/42 -> GET /users/42 -> detail params={id=42}
/users/42/posts -> GET /users/42/posts params={id=42}
/health -> OK
/missing -> 404Mari kita bedah baris-baris penting kode di atas:
class Route(val path: String): simpul pohon route. Setiap route punya path, daftar children, dan handler opsional. Struktur pohon memungkinkan nested route.fun handle(block: (Map<String, String>) -> String): mendaftar handler untuk route ini. Handler menerima map parameter (dari segmen{id}yang cocok) dan mengembalikan string body.fun match(segments, params): String?: algoritma rekursif. Bila segmen habis, jalankan handler. Bila tidak, cocokkan segmen pertama dengan children — parameter dinamis diikat ke map, route statis dicocokkan literal.if (child.path.startsWith("{") && child.path.endsWith("}")): deteksi parameter dinamis dengan sintaks{nama}. Bila cocok, simpan nilai keparams[key]lalu rekursi; bila gagal, hapus dariparamsagar backtrack bersih.routing { route("users") { get("{id}") { ... } } }: DSL yang meniru Ktor. Lambda dengan receiver memungkinkan sintaks deklaratif tanpa boilerplate.
Referensi Lanjutan #
- Ktor routing
— panduan resmi routing Ktor mencakup
routing { },route(), dan parameter path
Selanjutnya #
Setelah memahami routing, langkah berikutnya adalah membaca permintaan dan menghasilkan respons. Chapter Ktor Request dan Response akan membahas call.respondText, call.receive, status code, dan header HTTP.