Development Web

Quickly Prototyping a Ktor HTTP API

August 18, 2022
Quickly Prototyping a Ktor HTTP API

Step 1 — Use the Ktor project gen­er­a­tor #

With the Ktor project gen­er­a­tor, you can quick­ly gen­er­ate a Ktor project with min­i­mal con­fig­u­ra­tion. You can either gen­er­ate it via Jet­brains’ web­site here Gen­er­ate Ktor Project or use IntelliJ’s built-in project gen­er­a­tor. For the sake of this tuto­r­i­al I used the web generator.

Once you’re on the set­tings set­up page, feel free to con­fig­ure it how you’d like or set it up with the defaults. I used this configuration: 

Next, add your plu­g­ins, and for a bare bones REST API, I like to choose:

  • Rout­ing — For pro­vid­ing named routes with HTTP methods
  • Con­tent­Ne­go­ti­a­tion & kotlinx.serialization — For serializing/​deserializing JSON con­tent accept­ed and returned via our routes.
  • Call Log­ging — A nice to have to add on some addi­tion­al log­ging for requests that make it to our server.

You’ll then be able to down­load the project as a zip file, unzip it and open the project in your IDE (Intel­liJ was used for this tuto­r­i­al, as it han­dles the gra­dle sync­ing and build­ing for us) and we’ll move onto the next step.

Step 2 — Set­ting up your routes #

You’ll notice that the project has a Application.kt file that sets up the serv­er and con­fig­ures plu­g­ins that may act as inter­cep­tors or pro­vide addi­tion­al func­tion­al­i­ty like rout­ing. There is also the plugins fold­er which con­tains the Monitoring.kt plu­g­in for Call Log­ging, Routing.kt for our end­points, and Serialization.kt that tells the serv­er how to under­stand JSON serialization/​deserialization. Each plu­g­in typ­i­cal­ly gets installed via Ktor’s DSL (Domain Spe­cif­ic Lan­guage) using the install() func­tion. The excep­tion here is that rout­ing is set­up with­in the con­text of Application by call­ing routing {}.

The Routing.kt plu­g­in starts out with these contents: 

fun Application.configureRouting() {

    routing {
        get("/") {
            call.respondText("Hello World!")
        }
    }
}

Where it defines a sin­gle end­point accessed via a GET request to the base route / and responds with the text Hello World!.

You can run the serv­er by right-click on the Application.kt file and click­ing Run 'Application.kt'.

What we’ll do next is mod­i­fy this file to add on some addi­tion­al routes using that same DSL, as well as show how to sep­a­rate out the routes into sep­a­rate mod­ules”.

Below is the updat­ed example:

@Serializable
data class Book(val name: String, val author: String)
val listOfBooks = mutableListOf<Book>()

fun Route.books() {
    route("/books") {
        get {
            call.respond(listOfBooks)
        }
        get("{name?}") {
            val name = call.parameters["name"] ?: return@get call.respondText(
                "Missing book name",
                status = HttpStatusCode.BadRequest
            )
            val filteredBooksByName = listOfBooks.filter { it.name == name }
            call.respond(filteredBooksByName)
        }
        post {
            val newBook = call.receive<Book>()
            listOfBooks.add(newBook)
            call.respond(HttpStatusCode.Created, newBook)
        }
        delete("{name?}") {
            val name = call.parameters["name"] ?: return@delete call.respondText(
                "Missing book name",
                status = HttpStatusCode.BadRequest
            )
            // If any books were deleted
            when (listOfBooks.removeIf { it.name.equals(name, ignoreCase = true) }) {
                true -> call.respond(HttpStatusCode.Accepted)
                false -> call.respond(HttpStatusCode.NotFound)
            }
        }
    }
}

fun Route.authors() {
    route("/authors") {
        get {
            call.respond(listOfBooks.map{ it.author }.distinct())
        }
    }
}

fun Application.configureRouting() {
    routing {
        books()
        authors()
    }
}

In the above exam­ple, we cre­at­ed a basic data class that was marked with kotlinx.serialization’s @Serializable anno­ta­tion to make it seri­al­iz­able, and the ContentNegotiation plu­g­in we installed will tell the serv­er that when­ev­er we send or receive an object of this type, to con­vert it to/​from JSON back into this class.

We then just keep a list of Books with­in mem­o­ry to act as our data­base” for now and cre­ate two mod­ules, one for the /books end­point with fun Route.books() {} and anoth­er for the /authors end­point with fun Route.authors() {} and run these two func­tions with­in the con­fig­ureRout­ing block with a routing wrap­per, which comes from the Routing plu­g­in we installed. This gets ran with­in our Application.kt to tell the serv­er what routes we want to expose.

The meat of the rout­ing file involves the DSL functions:

  • route() {} — Defines a pre­fix­ing path to be used by our child routes called inside our function.
  • get() {} — Defines a GET end­point that can option­al­ly take para­me­ters with the syn­tax "{name?}" where name is the exposed vari­able of the path para­me­ter. You should call call.respond() and pass in an option­al sta­tus code and object, whose type is inferred, to respond to the client.
  • post() {} — When receiv­ing a POST body, you can call call.receive and define the type you’d like to parse from the client. Make sure to still call call.respond() to let the client know that you received their request.
  • delete() {} — Behaves sim­i­lar­ly to the func­tions above, but will han­dle DELETE methods.

Sum­ma­riz­ing the above, this is a great way to get a quick HTTP API up and run­ning and Ktor sup­ports even more func­tion­al­i­ty on top of this like: Type-safe rout­ing, authen­ti­ca­tion, Web­Sock­ets, and more. Feel free to check out the doc­u­men­ta­tion for Ktor.

Use­ful plu­g­ins to con­sid­er adding in lat­er #

The below plu­g­ins weren’t used for this tuto­r­i­al, but if I were to con­tin­ue pro­to­typ­ing the project, these are the plu­g­ins I’d opt for by default:

  • Caching­Head­ers
  • Authen­ti­ca­tion
  • CORS
  • Ses­sions
Josh Eldridge
Josh Eldridge
Software Developer

Stay in the loop with our latest content!

Select the topics you’re interested to receive our new relevant content in your inbox. Don’t worry, we won’t spam you.

Are You Solving the Right Problem?
Team

Are You Solving the Right Problem?

February 26, 2020

In Art’s Principles, renowned architect Art Gensler described part of his Design Thinking consultation process as “finding the right problem to solve.” This resonated with me, particularly as I’ve thought about how Michigan Software Labs interacts with clients in the Product Strategy Phase; something I believe sets us apart from other software development companies.

Read more
Using View Model Protocols to manage complex SwiftUI Views
iOS

Using View Model Protocols to manage complex SwiftUI Views

March 11, 2021

Managing complex screens or views that depend on asynchronous services or the need to pull in state from across your app can be tricky to get right. The most common way to address this in SwiftUI is by abstracting that logic into a dedicated view model for that piece of UI.

Read more
Tech Transformation Webinar
Business

Tech Transformation Webinar

July 12, 2021

Thank you for checking out the first ever webinar from MichiganLabs. It was great to hear from so many of you about how it helped you and your business. We have made the recording available below.

Read more
View more articles