Introduction to Kotlin Ktor

February 21, 2021

Kotlin is used to create different applications for different platforms. Server-side applications are no exception. Just like Java, Kotlin can be used to set up and run server side applications. There are several frameworks that can be used with Kotlin. For instance, all frameworks used with Java like Spring and Spark have support for Kotlin.

Then there are those specifically meant to be use with Kotlin. One of them is Ktor. Ktor was created by Jetbrains, the same open source company that brought us Kotlin 😄.

Ktor is used to create server side applications with asynchronous programming in mind. Ktor is lightweight and has a great support for coroutines.

It also has support for Kotlin Multiplatform; allowing cross platform capabilities.

In this article, we will take a quick look on how to set up a restful Ktor service. We will explore the basic architecture of applications using this framework.

Prerequisites

You will need:

  1. A basic understanding of the Kotlin programming language
  2. A basic understanding of HTTP methods
  3. IntelliJ IDE
  4. Postman or an equivalent software.

Step 1: Getting started

The first thing to do is to check whether the IDE has Ktor plugin installed. It will help us create a new ktor project.

Open your IDE and select “New Project”. Then go to the Ktor section to create a new ktor project. Ensure the settings are similar to the ones below.

starter

We are using Gradle and Netty engine for our server functionality. Gradle will handle our third party dependencies.

Once gradle build finishes, open the project’s build.gradle file and add the Kotlin serialization plugin. This library is used to convert Java objects to JSON and vice versa. You can also use Gson or Moshi but in this article, we will be using the serialization library.

plugins {
    ...
    id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
}

dependencies {
    // Ktor core
    implementation "io.ktor:ktor-server-core:$ktor_version"

    // Netty
    implementation "io.ktor:ktor-server-netty:$ktor_version"

    // Ktor serialization support
    implementation "io.ktor:ktor-serialization:$ktor_version"

    // Serialization
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"

}

Then open the application.conf file. This file configures the port and entry point of our application. For now we have only one module found in the Application.kt file.

Step 2: Setting up data

We can use the kotlin data class to define our data models. Go ahead and create a file named task and add the following code.

@Serializable
data class Task(val id: Int, val name: String, val description: String)

The Serializable annotation allows the serialization library to convert this data class to Json and vice versa.

For now we will be using an in-memory database. This means that our data will be lost once the application closes. Add the following code inside the Application.kt file.

private val tasks = mutableListOf<Task>()

We will perform CRUD operations on the data in this list.

Step 3: Defining routes

Routes are added to the routing lambda inside the module. Add the following code in the Application.module function.

fun Application.module(testing: Boolean = false) {
    install(ContentNegotiation){json()}
    routing {
        tasks()
    }

    // Bootstrap our application
    for (i in 0..5)
        tasks.add(Task(i, "Task $i", "Work on task $i"))
}

Since we will be posting data in the form of JSON, we add the content negotiation feature and enable JSON support. Content negotiation checks the Accept header in our requests to see whether the server can handle the data type used.

We also add a few dummy records in our list once the server starts.

To resolve the error in the routing object, create an extension function for the Route object. This will allow us to create endpoints for a specific route.

fun Route.tasks() {
    route("/tasks"){
    }
}

Here, we create an endpoint /tasks. We can then pass in the required HTTP methods in this route.

Step 4: Paths and HTTP methods

You can define paths for an endpoint as functions inside the route lambda with the corresponding HTTP method.

  1. Add the following method to get all the records.
// Get all Tasks
get {
    if (tasks.isNotEmpty())
        call.respond(tasks)
    else
        call.respondText("No Tasks available", status = HttpStatusCode.NotFound)
}

We first check if the list is not empty in order to return the data, otherwise, return an error. The call which is of type ApplicationCall gives us access to the request and response of the client.

Test the endpoint on GET: http://0.0.0.0:8080/tasks

  1. To get one Task using the id:
// Get one Task
get ("{id}"){
    val id = call.parameters["id"]?.toInt()
    val task = tasks.find { it.id == id }
    if (task == null)
        call.respondText("No task with that id exists", status = HttpStatusCode.NotFound)
    else
        call.respond(task)
}

In this path, we get the id parameter passed in the url. It is received as a string so we convert it to the required data type(Int). We then use it to get the task from our list.

Test the endpoint on GET http://0.0.0.0:8080/tasks/1

  1. To add Task objects to our list:
post {
    val task = call.receive<Task>()
    tasks.add(task)
    call.respondText("Task added successfully", status = HttpStatusCode.Accepted)
}

Test the endpoint on POST:http://0.0.0.0:8080/tasks. Pass in the required JSON conforming to the data type in the request body.

  1. To delete a Task using the id
delete ("{id}"){
    val id = call.parameters["id"]?.toInt()
    if (tasks.removeIf{it.id == id})
        call.respondText("Task deleted successfully", status = HttpStatusCode.Accepted)
    else
        call.respondText("No task with that id exists", status = HttpStatusCode.NotFound)
}

Test the endpoint on DELETE:http://0.0.0.0:8080/tasks/1

You can go ahead and test all the endpoints on Postman.

Conclusion

That is how create a restful service using ktor. It is very easy to set up and understand. You can go ahead and try other HTTP methods inside the /tasks route.

NOTE: The route paths are character sensitive. The forward slash at the end of the endpoint is not ignored hence you should be careful when creating and testing the endpoints

The full code of the tutorial can be found on GitHub. Feel free to raise any issue or PR.

Happy coding.


Peer Review Contributions by: Peter Kayere


About the author

Linus Muema

Linus Muema is a Kotlin and Javascript developer. He has a great passion for writing code, trying out new programming paradigms, and is always ready to help other developers.

This article was contributed by a student member of Section's Engineering Education Program. Please report any errors or innaccuracies to enged@section.io.