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