Ktor 2.1.3 Help

Type-safe requests

Ktor provides the Resources plugin that allows you to implement type-safe requests. To accomplish this, you need to create a class that describes resources available on a server 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-client-resources artifact in the build script:

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

You can learn more about artifacts required by the Ktor client from Adding client dependencies.

Install Resources

To install Resources, pass it to the install function inside a client configuration block:

import io.ktor.client.* import io.ktor.client.engine.cio.* import io.ktor.client.plugins.resources.* //... val client = HttpClient(CIO) { 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() { @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 make type-safe requests to this resource in the next section.

Make type-safe requests

To make a request to a typed resource, you need to pass a resource class instance to a request function (request, get, post, put, and so on). For example, the sample below shows how to make a request to the /articles path.

@Serializable @Resource("/articles") class Articles() fun main() { runBlocking { val client = HttpClient(CIO) { install(Resources) // ... } val getAllArticles = client.get(Articles()) } }

The example below shows how to make typed requests to the Articles resource created in Example: A resource for CRUD operations.

fun main() { runBlocking { val client = HttpClient(CIO) { install(Resources) defaultRequest { host = "0.0.0.0" port = 8080 url { protocol = URLProtocol.HTTP } } } val getAllArticles = client.get(Articles()) val newArticle = client.get(Articles.New()) val postArticle = client.post(Articles()) { setBody("Article content") } val getArticle = client.get(Articles.Id(id = 12)) val editArticlePage = client.get(Articles.Id.Edit(Articles.Id(id = 12))) val putArticle = client.put(Articles.Id(id = 12)) { setBody("New article content") } val deleteArticle = client.delete(Articles.Id(id = 12)) }

The defaultRequest function is used to specify a default URL for all requests.

Last modified: 16 September 2022