Build REST API with Kotlin and Ktor

Build REST API with Kotlin and Ktor

According to Stack Overflow survey from 2020, Kotlin is one of the most loved programming languages, so it is safe to say that JetBrains, a company that develops Kotlin, is doing an excellent job. Kotlin is most well known as a language for developing Android applications, but it can be used for developing backend services, such as RESTful API. JetBrains is also developing a framework named Ktor that can be used for developing RESTful API with Kotlin and as you can imagine, it is pretty great.

What is REST

REST, short for Representational state transfer, is a software architectural style that was created to guide the design and development of the architecture for internet communication. REST defines a set of constraints for how the architecture of an Internet-scale system, such as the Web, should behave.

A Web API (or Web Service) conforming to the REST architectural style is a REST API.

Ktor

Ktor is a web framework made by JetBrains with Kotlin programming language. In their own words:

Ktor is a framework to easily build connected applications – web applications, HTTP services, mobile and browser applications. Modern connected applications need to be asynchronous to provide the best experience to users, and Kotlin coroutines provide awesome facilities to do it in an easy and straightforward way.

Main goals of Ktor framework are to be lightweight, unopinionated, asynchronous and testable meaning that framework itself doesn't impose what technologies project is going to use, can be easily deployable and uses Kotlin coroutines to provide asynchronous operations.

Plugins

Ktor uses concept of Plugins to extend its functionalities. A plugin is a set of common functionalities that extend application logic, such as serialization or compression.

For example, typical request-response pipeline in Ktor:

image.png

Now, using Plugins in Ktor, previous request-response pipeline can be easily extended with functionalities to something like this:

image.png

Which plugin(s) you will add to the request-response pipeline depends on your preferences and application needs. Need Authentication? Fine, just install a plugin. Exception handling? Serialization? Great, just a simple plugin installation.

Plugins are configured during the initialization phase of the server using the install function which takes a Plugin as a parameter. You can install a plugin inside the embeddedServer call.

import io.ktor.features.*

fun main() {
    embeddedServer(Netty, port = 8080) {
        install(CORS)
        install(Compression)
        // ...
    }.start(wait = true)
}

or in desired module

import io.ktor.features.*

fun Application.module() {
    install(CORS)
    install(Compression)
    // ...
}

By default, Ktor does not activate any plugin(s), and it's up to developers to install the functionality their application needs.

Some of my personal favorite plugins are:

To find all other ready-to-use plugins, head over to Generate Ktor project tool.

Build REST API

First, we need to create Ktor project. There are two ways to create one, using online Generate Ktor project tool or, if you prefer, using Ktor plugin for IntelliJ IDEA.

Once the project is created, it should already have all necessary dependencies added for Ktor itself, but we need one more for serialization.

Dependencies:

dependencies {
    implementation("io.ktor:ktor-server-core:$ktor_version")
    implementation("io.ktor:ktor-server-netty:$ktor_version")
    implementation("ch.qos.logback:logback-classic:$logback_version")
    implementation("io.ktor:ktor-serialization:$ktor_version")
    testImplementation("io.ktor:ktor-server-tests:$ktor_version")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
}

Let's keep it simple and add every-tutorial-ever generic Customer class. We will use Kotlin's data class and kotlinx.serialization to serialize class to JSON, and from JSON to class in case of POST method. Since we are keeping it simple, the application will use in-memory storage. For any real-world application, you would use a database.

import kotlinx.serialization.Serializable

val customerStorage = mutableListOf<Customer>()

@Serializable
data class Customer(val id: String, val firstName: String, val lastName: String, val email: String)

Next step is to define routes. We want to respond to GET, POST, and DELETE requests on the /customer endpoint. As such, let's define our routes with the corresponding HTTP methods. Let's add a new file Routing.

Routing.kt:

fun Application.customerRouting() {
    routing {
        route("/customer") {
            get {
                if (customerStorage.isNotEmpty()) {
                    call.respond(customerStorage)
                } else {
                    call.respondText("No customers found", status = HttpStatusCode.NotFound)
                }
            }
            get("{id}") {
                val id = call.parameters["id"] ?: return@get call.respondText(
                    "Missing or malformed id",
                    status = HttpStatusCode.BadRequest
                )
                val customer =
                    customerStorage.find { it.id == id } ?: return@get call.respondText(
                        "No customer with id $id",
                        status = HttpStatusCode.NotFound
                    )
                call.respond(customer)
            }
            post {
                val customer = call.receive<Customer>()
                customerStorage.add(customer)
                call.respondText("Customer stored correctly", status = HttpStatusCode.Created)
            }
            delete("{id}") {
                val id = call.parameters["id"] ?: return@delete call.respond(HttpStatusCode.BadRequest)
                if (customerStorage.removeIf { it.id == id }) {
                    call.respondText("Customer removed correctly", status = HttpStatusCode.Accepted)
                } else {
                    call.respondText("Not Found", status = HttpStatusCode.NotFound)
                }
            }
        }
    }
}

Last step is to register previously defined routes and to install ContentNegotiation plugin so Ktor can handle serialization.

Application.kt:

fun main(args: Array<String>): Unit =
    io.ktor.server.netty.EngineMain.main(args)

fun Application.module() {
    install(ContentNegotiation) {
        json()
    }
    customerRouting()
}

Great, our simple REST API is now ready to use and to handle those sweet customer CRUD operations. :)

Conclusion

Thank you for reading and I hope this article was useful to you! In conclusion, this article went over Ktor framework, introduction to it and how you can build simple REST API using Ktor and Kotlin. If you are interested in Ktor, head over to official Ktor documentation for more thorough guides and explanations.


If you like my content and find it useful, please consider following me. If you are feeling extra generous, please consider buying me a coffee.

Connect with me on LinkedIn.