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

Looking for more like this?

Sign up for our monthly newsletter to receive helpful articles, case studies, and stories from our team.

A Study of Human-Centered Design Off Screen - Burning Man Festival
Design

A Study of Human-Centered Design Off Screen - Burning Man Festival

February 13, 2023

Kourtney examines the Burning Man Festival and its fascinating history of human-centered design

Read more
MichiganLabs’ approach to product strategy: Driving software success
Process Team

MichiganLabs’ approach to product strategy: Driving software success

February 12, 2024

Read more
A 3-part framework for getting your software project approved internally

A 3-part framework for getting your software project approved internally

September 25, 2024

Explore this strategic approach to securing internal buy-in for your custom software projects. The framework emphasizes starting with a lean business case, engaging key stakeholders across the organization to align economic, operational, and technical considerations, and embracing an iterative learning process to make informed decisions.

Read more
View more articles