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.

Finding Connectedness During COVID
Team

Finding Connectedness During COVID

February 17, 2021

When the world was turned upside down last March, our team was well set up for the technical side of what we do, but figuring out how to make us feel connected over a long stretch of remote work presented a new obstacle.

Read more
Chicago Roboto 2022 Retrospective
Android Development

Chicago Roboto 2022 Retrospective

August 11, 2022

Scott Schmitz shares some notes of interest from talks at Chicago Roboto 2022, an Android community conference, that took place August 1-2.

Read more
Working From Home: Lessons Learned From A Forced Experiment
Team

Working From Home: Lessons Learned From A Forced Experiment

March 19, 2021

Prior to the pandemic, WFH seemed like a frightening notion for many. Organizations wondered if employees could actually be productive working remote? Well, it’s been a year since our forced experiment first began and what has it taught us? …That it was great!…until it wasn’t.

Read more
View more articles