Ktor 2.0.3 Help

Type-safe routing

Ktor provides the Resources plugin that allows you to implement type-safe routing. To accomplish this, you need to create a class that should act as a typed route and then annotate this class using the @Resource keyword. Such classes should also have the @Serializable annotation provided by the kotlinx.serialization library.

Add dependencies

Add kotlinx.serialization

Given that resource classes should have the @Serializable annotation, you need to add the Kotlin serialization plugin as described in the Setup section.

Add Resources dependencies

To use Resources, you need to include the ktor-server-resources artifact in the build script:

implementation("io.ktor:ktor-server-resources:$ktor_version")
implementation "io.ktor:ktor-server-resources:$ktor_version"
<dependency> <groupId>io.ktor</groupId> <artifactId>ktor-server-resources-jvm</artifactId> <version>${ktor_version}</version> </dependency>

Install Resources

To install the Resources plugin, pass it to the install function in the application initialization code. Depending on the way used to create a server, this can be the embeddedServer function call ...

import io.ktor.server.application.* import io.ktor.server.resources.* // ... fun main() { embeddedServer(Netty, port = 8080) { install(Resources) // ... }.start(wait = true) }

... or a specified module.

import io.ktor.server.application.* import io.ktor.server.resources.* // ... fun Application.module() { install(Resources) // ... }

Create resource classes

Each resource class should have the following annotations:

Below we'll take a look at several examples of resource classes - defining a single path segment, query and path parameters, and so on.

Resource URL

The example below shows how to define the Articles class that specifies a resource responding on the /articles path.

import io.ktor.resources.* @Serializable @Resource("/articles") class Articles()

Resources with a query parameter

The Articles class below has the sort string property that acts as a query parameter and allows you to define a resource responding on the following path with the sort query parameter: /articles?sort=new.

@Serializable @Resource("/articles") class Articles(val sort: String? = "new")

Note that properties can be primitives or types annotated with the @Serializable annotation.

Resources with nested classes

You can nest classes to create resources that contain several path segments. Note that in this case nested classes should have a property with an outer class type. The example below shows a resource responding on the /articles/new path.

@Serializable @Resource("/articles") class Articles() { @Serializable @Resource("new") class New(val parent: Articles = Articles()) }

Resources with a path parameter

The example below demonstrates how to add the nested {id} integer path parameter that matches a path segment and captures it as a parameter named id.

@Serializable @Resource("/articles") class Articles() { @Serializable @Resource("{id}") class Id(val parent: Articles = Articles(), val id: Long) }

As an example, this resource can be used to respond on /articles/12.

Example: A resource for CRUD operations

Let's summarize the examples above and create the Articles resource for CRUD operations.

@Serializable @Resource("/articles") class Articles(val sort: String? = "new") { @Serializable @Resource("new") class New(val parent: Articles = Articles()) @Serializable @Resource("{id}") class Id(val parent: Articles = Articles(), val id: Long) { @Serializable @Resource("edit") class Edit(val parent: Id) } }

This resource can be used to list all articles, post a new article, edit it, and so on. We'll see how to define route handlers for this resource in the next chapter.

Define route handlers

To define a route handler for a typed resource, you need to pass a resource class to a verb function (get, post, put, and so on). For example, a route handler below responds on the /articles path.

@Serializable @Resource("/articles") class Articles() fun Application.module() { install(Resources) routing { get<Articles> { // Get all articles ... call.respondText("List of articles") } } }

The example below shows how to define route handlers for the Articles resource created in Example: A resource for CRUD operations. Note that inside the route handler you can access the Article as a parameter and obtain its property values.

fun Application.module() { install(Resources) routing { get<Articles> { article -> // Get all articles ... call.respondText("List of articles sorted starting from ${article.sort}") } get<Articles.New> { // Show a page with fields for creating a new article ... call.respondText("Create a new article") } post<Articles> { // Save an article ... call.respondText("An article is saved", status = HttpStatusCode.Created) } get<Articles.Id> { article -> // Show an article with id ${article.id} ... call.respondText("An article with id ${article.id}", status = HttpStatusCode.OK) } get<Articles.Id.Edit> { article -> // Show a page with fields for editing an article ... call.respondText("Edit an article with id ${article.parent.id}", status = HttpStatusCode.OK) } put<Articles.Id> { article -> // Update an article ... call.respondText("An article with id ${article.id} updated", status = HttpStatusCode.OK) } delete<Articles.Id> { article -> // Delete an article ... call.respondText("An article with id ${article.id} deleted", status = HttpStatusCode.OK) } } }

You can find the full example here: resource-routing.

Last modified: 28 June 2022