Routing in Ktor

Routing is at the heart of a Ktor application. It’s what allows incoming requests to be handled. When a request such as /hello is made, the routing mechanism in Ktor allows us to define how we want this to request to be served.

This is accomplished using the Routing Feature which can be installed in any Ktor server application:

  1. install(Routing) {
  2. route("/hello", HttpMethod.Get) {
  3. handle {
  4. call.respondText("Hello")
  5. }
  6. }
  7. }

route takes three parameters:

  • The URL pattern

  • The Verb, which can be GET, POST, PUT, DELETE, HEAD, OPTION, or PATCH

  • The Handler, which provides us with access to handling the request

Given the Routing Feature is so common in any application, there is a convenient routing function that makes it simpler to install routing:

  1. routing {
  2. route("/hello", HttpMethod.Get) {
  3. handle {
  4. call.respondText("Hello")
  5. }
  6. }
  7. }

We can see that we’ve replaced install(Routing) with the routing function.

Verbs as functions

Similar to how routing simplifies usage of the Routing Feature, Ktor provides a series of functions that make defining route handlers much easier and more concise. The previous code can be expressed as:

  1. install(Routing) {
  2. get("/hello") {
  3. call.respondText("Hello")
  4. }
  5. }

We can see that the route function is replaced with a get function that now only needs to take the URL and the code to handle the request. In a similar way Ktor provides functions for all the other verbs, that is put, post, head, and so on.

Defining multiple route handlers

If we want to define multiple route handlers, which of course is the case for any application, we can just add them to the routing function:

  1. routing {
  2. get("/customer/{id}") {
  3. }
  4. post("/customer") {
  5. }
  6. get("/order/{id}") {
  7. }
  8. }

In this case, each route has its own function and responds to the specific endpoint and HTTP verb.

An alternative way is to group these by paths, whereby we define the path and then place the verbs for that path as nested functions, using the route function:

  1. routing {
  2. route("/customer") {
  3. get {
  4. }
  5. post {
  6. }
  7. }
  8. }

Independently of how we do the grouping, Ktor also allows us to have sub-routes as parameters to route functions. The following example shows us how to respond to incoming requests to /order/shipment:

  1. routing {
  2. route("/order") {
  3. route("/shipment") {
  4. get {
  5. }
  6. post {
  7. }
  8. }
  9. }
  10. }

Using Route Extension Functions

A common pattern is to use extension functions on the Route type to define the actual routes, allowing us easy access to the verbs and remove clutter of having all routes in a single routing function. We can apply this pattern independently of how we decide to group routes. As such, the first example could be represented in a cleaner way:

  1. routing {
  2. customerByIdRoute()
  3. createCustomerRoute()
  4. orderByIdRoute()
  5. createOrder()
  6. }
  7. fun Route.customerByIdRoute() {
  8. get("/customer/{id}") {
  9. }
  10. }
  11. fun Route.createCustomerRoute() {
  12. post("/customer") {
  13. }
  14. }
  15. fun Route.orderByIdRoute() {
  16. get("/order/{id}") {
  17. }
  18. }
  19. fun Route.createOrder() {
  20. post("/order") {
  21. }
  22. }

For our application to scale when it comes to maintainability, it is recommended to follow certain Structuring patterns.

note

Routing in Ktor - 图1

For more advanced topics around routing please see [Advanced Routing].