Ktor 2.1.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 to the application, pass it to the install function in the specified module. The code snippets below show how to install Resources...

  • ... inside the embeddedServer function call.

  • ... inside the explicitly defined module, which is an extension function of the Application class.

import io.ktor.server.application.* import io.ktor.server.resources.* // ... fun main() { embeddedServer(Netty, port = 8080) { install(Resources) // ... }.start(wait = true) }
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) } } }

Here are several tips on handling requests for each endpoint:

  • get<Articles>

    This route handler is supposed to return all articles sorted according to the sort query parameter. For instance, this might be an HTML page or a JSON object with all articles.

  • get<Articles.New>

    This endpoint responds with a web form containing fields for creating a new article.

  • post<Articles>

    The post<Articles> endpoint is supposed to receive parameters sent using a web form. Ktor also allows you to receive JSON data as an object using the ContentNegotiation plugin.

  • get<Articles.Id>

    This route handler is supposed to return an article with the specified identifier. This might be an HTML page showing an article or a JSON object with article data.

  • get<Articles.Id.Edit>

    This endpoint responds with a web form containing fields for editing an existing article.

  • put<Articles.Id>

    Similarly to the post<Articles> endpoint, the put handler receives form parameters sent using a web form.

  • delete<Articles.Id>

    This route handler deletes an article with the specified identifier.

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

Besides using resource definitions for routing, they can also be used to build links. This is sometimes called reverse routing. Building links from resources might be helpful if you need to add these links to an HTML document created with HTML DSL or if you need to generate a redirection response.

The Resources plugin extends Application with the overloaded href method, which allows you to generate a link from a Resource. For instance, the code snippet below creates a link for the Edit resource defined above:

val link: String = href(Articles.Id.Edit(Articles.Id(id = 123)))

Since the grandparent Articles resource defines the sort query parameter with the default value new, the link variable contains:

/articles/123/edit?sort=new

To generate URLs that specify the host and protocol, you can supply the href method with URLBuilder. Additional query params can also be specified using the URLBuilder, as shown in this example:

val urlBuilder = URLBuilder(URLProtocol.HTTPS, "ktor.io", parameters = parametersOf("token", "123")) href(Articles(sort = null), urlBuilder) val link: String = urlBuilder.buildString()

The link variable subsequently contains:

https://ktor.io/articles?token=123

The example below shows how to add links built from resources to the HTML response:

get { call.respondHtml { body { this@module.apply { p { val link: String = href(Articles()) a(link) { +"Get all articles" } } p { val link: String = href(Articles.New()) a(link) { +"Create a new article" } } p { val link: String = href(Articles.Id.Edit(Articles.Id(id = 123))) a(link) { +"Edit an exising article" } } p { val urlBuilder = URLBuilder(URLProtocol.HTTPS, "ktor.io", parameters = parametersOf("token", "123")) href(Articles(sort = null), urlBuilder) val link: String = urlBuilder.buildString() i { a(link) { +link } } } } } } }

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

Last modified: 18 October 2022