
Ktor — Backend made simple
Your first API in less than 20 minutes
This would be a 2 part series. First, we would set up a Ktor server in less than 20 minutes, and then we would deep dive into how Ktor really works, what is going on under the hood.
What is Ktor ?
From the official docs here , 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.
Let's dive into the code !!
Project Setup
Ktor has a few ways of setting up a preconfigured Gradle project: start.ktor.io and the Ktor IntelliJ IDEA plugin. Or you can clone it from here
You would find a resources folder already available to you which has a file called application.conf
which configures the entry point for our application. It should look something like this
ktor {
deployment {
port = 8080
port = ${?PORT}
}
application {
modules = [ com.akshay.sampleKtor.ApplicationKt.module ]
}
}
This corresponds to the Application.module()
function in Application.kt
, which currently doesn't do anything and looks like this.
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)fun Application.module() {}
Defining models
Create a folder under src->main
called models
and add a file called Movies.kt
and add the following code to it.
import kotlinx.serialization.Serializable@Serializable
data class Movie(val id: String, val movieName: String, val movieGenre: String, val releaseDate: String)
To not complicate the code, for this tutorial we’ll be using in-memory storage (i.e. a mutable list of. Movie
s) – in a real application, we would be storing this information in a database so that it doesn't get lost after restarting our application. We can simply add this line to the top level of the Movie.kt
file:
val movieStorage = mutableListOf<Movie>(
Movie(id = "101",movieName = "Dark",movieGenre = "thriller",releaseDate = "2017"),
Movie(id = "102",movieName = "Inception",movieGenre = "sci-fi",releaseDate = "2015")
)
Defining routes
We want to respond to GET
, POST
, and DELETE
requests on the /movie
endpoint. As such, let's define our routes with the corresponding HTTP methods.Create a folder routes
under src->main
and add a file MovieRoutes.kt
which will contain the following code.
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import models.Movie
import models.movieStorage
/**
* Routing for movies
*/
fun Route.movieRouting() {
/**
* this is an all in one route for movies
*/
route("/movie") {
/**
* this would get all movies in our temporary database
*/
get {
if (movieStorage.isNotEmpty()) {
call.respond(movieStorage)
} else {
call.respondText("No movies found", status = HttpStatusCode.NotFound)
}
}
/**
* this would get movies with provided id
*/
get("{id}") {
val id = call.parameters["id"] ?: return@get call.respondText(
"Missing or malformed id",
status = HttpStatusCode.BadRequest
)
val movie =
movieStorage.find { it.id == id } ?: return@get call.respondText(
"No movie with id $id",
status = HttpStatusCode.NotFound
)
call.respond(movie)
}
/**
* this would add a movie to our temporary database
*/
post {
val movie = call.receive<Movie>()
movieStorage.add(movie)
call.respondText("Movie stored correctly", status = HttpStatusCode.Accepted)
}
/**
* this would delete a movie from our temporary database
*/
delete("{id}") {
val id = call.parameters["id"] ?: return@delete call.respond(HttpStatusCode.BadRequest)
if (movieStorage.removeIf { it.id == id }) {
call.respondText("Movie removed correctly", status = HttpStatusCode.Accepted)
} else {
call.respondText("Not Found", status = HttpStatusCode.NotFound)
}
}
}
}
/**
* registering a movie route
*/
fun Application.registerMovieRoutes() {
routing {
movieRouting()
}
}
In order for this to work, we need to enable content negotiation in Ktor. Add the below line to your Application.kt
file. When a client makes a request, content negotiation allows the server to examine the Accept
header, see if it can serve this specific type of content, and if so, return the result.
fun Application.module() {
install(ContentNegotiation) {
json()
}
}
And finally, we would like all this to come in place by registering our movie route in our Application.module
fun Application.module() {
install(ContentNegotiation) {
json()
}
registerMovieRoutes()
}
Run application from the menu. Hoila !! our API server is up and running.
Test your API with Postman collection uploaded here. If you face any difficulties while running this app check out my GitHub repo