Ktor is an open-source framework for building asynchronous servers and clients - developed in Kotlin, for Kotlin, by the Kotlin team at JetBrains! It takes full advantage of language features to provide a concise DSL, making code easy to write, read and maintain. It’s also built from the ground up with coroutines, which will ktor for all your async needs (I won’t apologise for that one). Ktor comes in two main packages - a JVM-based HTTP server framework and a multiplatform HTTP client, but let’s focus on the server-side for today.

Giving it a spin

Hello World in Ktor actually fits in one pre-2019 tweet (122/140 characters without spaces):

fun main() {
    embeddedServer(Netty, port = 8080) {
        routing {
            get("/") {
                call.respondText("Hello World!")
            }
        }
    }.start(wait = true)
}

Let’s build on it a bit and create a (very) simple REST microservice - in one Kotlin file. Just as the above, we’ll begin by declaring an embedded server in our main method and starting it:

fun main() {
    embeddedServer(Netty, port = 8080) {

    }.start(wait = true)
}

This alone will start up Ktor running with Netty on port 8080 in less than a second, although Ktor can also run on Jetty, Tomcat and Docker containers. Within the embeddedServer’s lambda, we get access to Ktor’s Application instance, which represents a running application that can handle requests. It’s within this block that we configure most aspects of the service, so let’s add a simple GET request:

fun main() {
    embeddedServer(Netty, port = 8080) {
        routing {
            get("/oi") {
                call.respondText("Winter is coming...")
            }
        }
    }.start(wait = true)
}

Within the routing block is where, unsurprisingly, route mappings are defined. Within that, we have a get block where an execution context is provided - this gives us access to the call field, which represents the current state of the request/response objects (think filter chain in Spring). As would be expected, we can access request information such as headers and query parameters, and commit the response using respondText().

An important thing to note here is that respondText() is a suspended function. The get lambda actually provides a coroutine scope, which demonstrates the power of combining DSLs and coroutines - calling respondText() looks extactly like calling a normal function (I bet you hadn’t even noticed).

Another powerful feature of Ktor is it’s extension model, with which most of the provided functionality is implemented. Let’s modify the app to make it RESTful and return JSON objects:

fun main() {
    embeddedServer(Netty, port = 8080) {
        install(ContentNegotiation) {
            jackson()
        }
        routing {
            get("/oi") {
                val characters = listOf("Eddard Stark", "Tywin Lannister", "Robert Baratheon")
                call.respond(mapOf("characters" to characters))
            }
        }
    }.start(wait = true)
}

By simply calling install(), we have included the Content Negotiation feature (bundled with Ktor) and configured it to use JSON via the Jackson library. The feature intercepts the execution context and changes the behaviour of the app in a reusable way - and the best part is, it’s exceedingly easy for 3rd parties to create their own extensions and install them just as above. Other features that ship with Ktor are well-known classics: Authentication, templating, CORS, logging, testing and more.

By calling the GET endpoint we created, we receive:

{
    "characters": [
        "Eddard Stark",
        "Tywin Lannister",
        "Robert Baratheon"
    ]
}

In Konclusion

And that’s it! Of course, the example above is very simple and could be extended with many other Ktor features - for example by defining modules rather than inlining all of the configuration, doing some templating or authentication.

Although there are other battle-tested frameworks with first-class Kotlin support out there (looking at you Spring), Ktor is a worthy alternative for new projects (especially microservices) thanks to its use of DSLs, native coroutines support, easy extensibility and being lightweight.

If you want to learn more about Ktor you can have a gander at their docs or sample projects. If you just want to dive into some code, you can generate a Ktor project here (IntelliJ plugin is also available).

Have a Merry Khristmas!