0
# Routing System
1
2
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.
3
4
## Core Routing Classes
5
6
### RoutingNode
7
8
```kotlin { .api }
9
class RoutingNode(
10
parent: RoutingNode?,
11
selector: RouteSelector,
12
developmentMode: Boolean,
13
environment: ApplicationEnvironment
14
) : ApplicationCallPipeline, Route {
15
val parent: RoutingNode?
16
val selector: RouteSelector
17
val children: List<RoutingNode>
18
19
fun createChild(selector: RouteSelector): RoutingNode
20
fun handle(body: PipelineContext<Unit, PipelineCall>.() -> Unit)
21
operator fun invoke(body: Route.() -> Unit): Route
22
}
23
```
24
25
### Route Interface
26
27
```kotlin { .api }
28
interface Route : ApplicationCallPipeline {
29
val parent: Route?
30
val selector: RouteSelector
31
32
// Route building functions
33
fun createChild(selector: RouteSelector): Route
34
fun handle(handler: PipelineInterceptor<Unit, PipelineCall>)
35
}
36
```
37
38
## Basic Route Creation
39
40
### String Path Routing
41
42
```kotlin { .api }
43
// Route for exact string path
44
fun Route.route(path: String, build: Route.() -> Unit): Route
45
46
// Route for string path with specific HTTP method
47
fun Route.route(path: String, method: HttpMethod, build: Route.() -> Unit): Route
48
49
// Examples
50
routing {
51
route("/users") {
52
// Nested routes under /users
53
route("/profile") {
54
// Handles /users/profile
55
}
56
}
57
58
route("/api", HttpMethod.Get) {
59
// Only GET requests to /api/*
60
}
61
}
62
```
63
64
### Regex Path Routing
65
66
```kotlin { .api }
67
// Route for regex path pattern
68
fun Route.route(path: Regex, build: Route.() -> Unit): Route
69
70
// Example
71
routing {
72
route(Regex("/files/.*\\.(jpg|png|gif)")) {
73
// Matches image file paths
74
handle {
75
val path = call.request.path()
76
call.respondText("Image file: $path")
77
}
78
}
79
}
80
```
81
82
## HTTP Method Routes
83
84
### Standard HTTP Methods
85
86
```kotlin { .api }
87
// HTTP method route functions
88
fun Route.get(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
89
fun Route.post(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
90
fun Route.put(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
91
fun Route.delete(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
92
fun Route.patch(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
93
fun Route.head(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
94
fun Route.options(path: String = "", body: PipelineInterceptor<Unit, ApplicationCall>): Route
95
96
// Generic method route
97
fun Route.method(method: HttpMethod, body: Route.() -> Unit): Route
98
```
99
100
### HTTP Method Examples
101
102
```kotlin { .api }
103
routing {
104
// GET route
105
get("/") {
106
call.respondText("Welcome to Ktor!")
107
}
108
109
// POST route with path
110
post("/users") {
111
val user = call.receive<User>()
112
val created = userService.create(user)
113
call.respond(HttpStatusCode.Created, created)
114
}
115
116
// PUT route for updates
117
put("/users/{id}") {
118
val id = call.parameters["id"]!!
119
val user = call.receive<User>()
120
val updated = userService.update(id, user)
121
call.respond(updated)
122
}
123
124
// DELETE route
125
delete("/users/{id}") {
126
val id = call.parameters["id"]!!
127
userService.delete(id)
128
call.respond(HttpStatusCode.NoContent)
129
}
130
131
// Custom method handling
132
method(HttpMethod.Patch) {
133
route("/users/{id}") {
134
handle {
135
// Handle PATCH request
136
}
137
}
138
}
139
}
140
```
141
142
## Parameter Routing
143
144
### Path Parameters
145
146
```kotlin { .api }
147
// Route with parameter capture
148
get("/users/{id}") {
149
val userId = call.parameters["id"]
150
call.respondText("User ID: $userId")
151
}
152
153
// Multiple parameters
154
get("/users/{userId}/posts/{postId}") {
155
val userId = call.parameters["userId"]
156
val postId = call.parameters["postId"]
157
call.respond(getPost(userId, postId))
158
}
159
160
// Optional parameters with wildcards
161
get("/files/{path...}") {
162
val filePath = call.parameters["path"]
163
call.respondText("File path: $filePath")
164
}
165
```
166
167
### Query Parameters
168
169
```kotlin { .api }
170
// Parameter routing functions
171
fun Route.param(name: String, value: String, build: Route.() -> Unit): Route
172
fun Route.param(name: String, build: Route.() -> Unit): Route
173
fun Route.optionalParam(name: String, build: Route.() -> Unit): Route
174
175
// Examples
176
routing {
177
// Route only when parameter has specific value
178
param("version", "v1") {
179
get("/api/users") {
180
// Only matches /api/users?version=v1
181
}
182
}
183
184
// Route that captures any parameter value
185
param("format") {
186
get("/data") {
187
val format = call.parameters["format"]
188
// Matches /data?format=json, /data?format=xml, etc.
189
}
190
}
191
192
// Route for optional parameter
193
optionalParam("limit") {
194
get("/items") {
195
val limit = call.parameters["limit"]?.toIntOrNull() ?: 10
196
// Works with or without ?limit=N
197
}
198
}
199
}
200
```
201
202
### Parameter Access
203
204
```kotlin { .api }
205
get("/search") {
206
// Query parameters
207
val query = call.request.queryParameters["q"]
208
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 1
209
val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 20
210
211
// Path parameters
212
val category = call.parameters["category"]
213
214
val results = searchService.search(query, category, page, size)
215
call.respond(results)
216
}
217
```
218
219
## Header and Host Routing
220
221
### Header-Based Routing
222
223
```kotlin { .api }
224
// Route for specific header value
225
fun Route.header(name: String, value: String, build: Route.() -> Unit): Route
226
227
// Examples
228
routing {
229
header("Content-Type", "application/json") {
230
post("/api/data") {
231
// Only matches requests with JSON content type
232
val data = call.receive<JsonObject>()
233
call.respond(processJsonData(data))
234
}
235
}
236
237
header("Accept", "application/xml") {
238
get("/api/users") {
239
// Only matches requests that accept XML
240
val users = userService.getAllUsers()
241
call.respond(users.toXml())
242
}
243
}
244
245
header("Authorization") { // Any Authorization header
246
get("/protected") {
247
val token = call.request.header("Authorization")
248
// Handle authenticated requests
249
}
250
}
251
}
252
```
253
254
### Host and Port Routing
255
256
```kotlin { .api }
257
// Route for specific host
258
fun Route.host(host: String, build: Route.() -> Unit): Route
259
260
// Route for specific port
261
fun Route.port(port: Int, build: Route.() -> Unit): Route
262
263
// Examples
264
routing {
265
host("api.example.com") {
266
get("/v1/users") {
267
// Only matches requests to api.example.com
268
}
269
}
270
271
host("admin.example.com") {
272
get("/dashboard") {
273
// Admin interface on subdomain
274
}
275
}
276
277
port(8443) {
278
get("/secure") {
279
// Only matches requests on port 8443
280
}
281
}
282
}
283
```
284
285
## Route Selectors
286
287
### RouteSelector Classes
288
289
```kotlin { .api }
290
// Base route selector interface
291
abstract class RouteSelector {
292
abstract fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation
293
}
294
295
// Path segment selector
296
class PathSegmentRouteSelector(val value: String) : RouteSelector
297
298
// HTTP method selector
299
class HttpMethodRouteSelector(val method: HttpMethod) : RouteSelector
300
301
// Parameter selector
302
class ParameterRouteSelector(val name: String) : RouteSelector
303
304
// HTTP header selector
305
class HttpHeaderRouteSelector(val name: String, val value: String? = null) : RouteSelector
306
```
307
308
### Custom Route Selectors
309
310
```kotlin { .api }
311
// Create custom route selector
312
class CustomRouteSelector : RouteSelector() {
313
override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
314
// Custom routing logic
315
return if (customCondition(context)) {
316
RouteSelectorEvaluation.Constant
317
} else {
318
RouteSelectorEvaluation.Failed
319
}
320
}
321
322
private fun customCondition(context: RoutingResolveContext): Boolean {
323
// Implement custom routing condition
324
return context.call.request.userAgent()?.contains("MyApp") == true
325
}
326
}
327
328
// Use custom selector
329
fun Route.customRoute(build: Route.() -> Unit): Route {
330
return createChild(CustomRouteSelector()).apply(build)
331
}
332
```
333
334
## Routing Pipeline Classes
335
336
### RoutingPipelineCall
337
338
```kotlin { .api }
339
class RoutingPipelineCall(
340
call: ApplicationCall,
341
route: Route,
342
receivePipeline: ApplicationReceivePipeline,
343
sendPipeline: ApplicationSendPipeline,
344
parameters: Parameters
345
) : PipelineCall {
346
val route: Route
347
// Additional routing-specific context
348
}
349
```
350
351
### RoutingResolveContext
352
353
```kotlin { .api }
354
class RoutingResolveContext(
355
val routing: RoutingNode,
356
val call: ApplicationCall,
357
val parameters: Parameters
358
) {
359
// Context for route resolution process
360
}
361
```
362
363
### RoutingPath
364
365
```kotlin { .api }
366
class RoutingPath(val parts: List<RoutingPathSegment>) {
367
companion object {
368
fun parse(path: String): RoutingPath
369
fun root(): RoutingPath
370
}
371
}
372
373
sealed class RoutingPathSegment {
374
object Root : RoutingPathSegment()
375
data class Constant(val value: String) : RoutingPathSegment()
376
data class Parameter(val name: String) : RoutingPathSegment()
377
}
378
```
379
380
## Advanced Routing Patterns
381
382
### Route Groups and Organization
383
384
```kotlin { .api }
385
routing {
386
// API versioning
387
route("/api") {
388
route("/v1") {
389
userRoutesV1()
390
productRoutesV1()
391
}
392
393
route("/v2") {
394
userRoutesV2()
395
productRoutesV2()
396
}
397
}
398
399
// Admin routes
400
route("/admin") {
401
authenticate("admin") {
402
userManagementRoutes()
403
systemRoutes()
404
}
405
}
406
407
// Public routes
408
staticRoutes()
409
authRoutes()
410
}
411
412
fun Route.userRoutesV1() {
413
route("/users") {
414
get {
415
call.respond(userService.getAllUsers())
416
}
417
418
post {
419
val user = call.receive<User>()
420
call.respond(userService.createUser(user))
421
}
422
423
route("/{id}") {
424
get {
425
val id = call.parameters["id"]!!
426
call.respond(userService.getUser(id))
427
}
428
429
put {
430
val id = call.parameters["id"]!!
431
val user = call.receive<User>()
432
call.respond(userService.updateUser(id, user))
433
}
434
435
delete {
436
val id = call.parameters["id"]!!
437
userService.deleteUser(id)
438
call.respond(HttpStatusCode.NoContent)
439
}
440
}
441
}
442
}
443
```
444
445
### Content Negotiation in Routes
446
447
```kotlin { .api }
448
routing {
449
route("/api/data") {
450
// JSON endpoint
451
header("Accept", "application/json") {
452
get {
453
call.respond(dataService.getData())
454
}
455
}
456
457
// XML endpoint
458
header("Accept", "application/xml") {
459
get {
460
call.respondText(
461
dataService.getData().toXml(),
462
ContentType.Application.Xml
463
)
464
}
465
}
466
467
// CSV endpoint
468
header("Accept", "text/csv") {
469
get {
470
call.respondText(
471
dataService.getData().toCsv(),
472
ContentType.Text.CSV
473
)
474
}
475
}
476
}
477
}
478
```
479
480
### Conditional Routing
481
482
```kotlin { .api }
483
routing {
484
// Development-only routes
485
if (developmentMode) {
486
route("/debug") {
487
get("/routes") {
488
val routes = collectRoutes(this@routing)
489
call.respond(routes)
490
}
491
492
get("/config") {
493
call.respond(application.environment.config.toMap())
494
}
495
}
496
}
497
498
// Feature-flagged routes
499
if (featureFlags.isEnabled("new-api")) {
500
route("/api/v3") {
501
newApiRoutes()
502
}
503
}
504
}
505
```
506
507
## Trailing Slash Configuration
508
509
### IgnoreTrailingSlash
510
511
```kotlin { .api }
512
// Configure trailing slash handling
513
install(IgnoreTrailingSlash)
514
515
// Now these routes are equivalent:
516
// GET /users
517
// GET /users/
518
519
routing {
520
get("/users") {
521
call.respondText("Users endpoint")
522
}
523
// This will handle both /users and /users/
524
}
525
```
526
527
## Route Information and Debugging
528
529
### Route Collection
530
531
```kotlin { .api }
532
fun collectRoutes(root: Route): List<RouteInfo> {
533
val routes = mutableListOf<RouteInfo>()
534
535
fun collectRoutesRecursive(route: Route, path: String = "") {
536
val currentPath = path + when (val selector = route.selector) {
537
is PathSegmentRouteSelector -> "/${selector.value}"
538
is ParameterRouteSelector -> "/{${selector.name}}"
539
is HttpMethodRouteSelector -> " [${selector.method.value}]"
540
else -> ""
541
}
542
543
if (route.children.isEmpty()) {
544
routes.add(RouteInfo(currentPath, route.selector))
545
}
546
547
route.children.forEach { child ->
548
collectRoutesRecursive(child, currentPath)
549
}
550
}
551
552
collectRoutesRecursive(root)
553
return routes
554
}
555
556
data class RouteInfo(val path: String, val selector: RouteSelector)
557
```
558
559
## Complete Routing Example
560
561
```kotlin { .api }
562
fun Application.configureRouting() {
563
routing {
564
// Root endpoint
565
get("/") {
566
call.respondText("Welcome to our API")
567
}
568
569
// API routes
570
route("/api/v1") {
571
// Content-Type based routing
572
header("Content-Type", "application/json") {
573
574
// User management
575
route("/users") {
576
get {
577
val page = call.request.queryParameters["page"]?.toIntOrNull() ?: 0
578
val size = call.request.queryParameters["size"]?.toIntOrNull() ?: 20
579
call.respond(userService.getUsers(page, size))
580
}
581
582
post {
583
val user = call.receive<CreateUserRequest>()
584
val created = userService.createUser(user)
585
call.respond(HttpStatusCode.Created, created)
586
}
587
588
route("/{id}") {
589
get {
590
val id = call.parameters["id"]!!
591
val user = userService.getUser(id)
592
if (user != null) {
593
call.respond(user)
594
} else {
595
call.respond(HttpStatusCode.NotFound)
596
}
597
}
598
599
put {
600
val id = call.parameters["id"]!!
601
val updateRequest = call.receive<UpdateUserRequest>()
602
val updated = userService.updateUser(id, updateRequest)
603
call.respond(updated)
604
}
605
606
delete {
607
val id = call.parameters["id"]!!
608
userService.deleteUser(id)
609
call.respond(HttpStatusCode.NoContent)
610
}
611
612
// User posts
613
route("/posts") {
614
get {
615
val userId = call.parameters["id"]!!
616
call.respond(postService.getUserPosts(userId))
617
}
618
619
post {
620
val userId = call.parameters["id"]!!
621
val post = call.receive<CreatePostRequest>()
622
val created = postService.createPost(userId, post)
623
call.respond(HttpStatusCode.Created, created)
624
}
625
}
626
}
627
}
628
629
// Search endpoint with query parameters
630
get("/search") {
631
val query = call.request.queryParameters["q"]
632
?: return@get call.respond(HttpStatusCode.BadRequest, "Missing query parameter")
633
val type = call.request.queryParameters["type"] ?: "all"
634
val results = searchService.search(query, type)
635
call.respond(results)
636
}
637
}
638
}
639
640
// Health check (no content-type requirement)
641
get("/health") {
642
call.respondText("OK", ContentType.Text.Plain)
643
}
644
645
// Static files
646
route("/static/{path...}") {
647
get {
648
val path = call.parameters["path"]!!
649
val file = File("static/$path")
650
if (file.exists() && file.isFile) {
651
call.respondFile(file)
652
} else {
653
call.respond(HttpStatusCode.NotFound)
654
}
655
}
656
}
657
}
658
}
659
```
660
661
This comprehensive routing documentation covers all aspects of Ktor's routing system, from basic URL matching to advanced patterns and conditional routing strategies.