Starting point – Initial Web API
We will start to create an initial Ktor project. Tutorial
Serve static content
Content negotiation and serialization plugins
The web API will transfer and receive objects in JSON format. In order to communicate and receive these objects we must first ensure that the client and server agree about the content. This step is called content negotiation. Basically, it will be decided on which format and standard is used to send data (in our JSON).
The second part is to configure the application how to serialize and deserialize objects from and to JSON. Kotlin has built-in support for serializing objects. We will be using the Serialization annotation.
First, we must adapt our build.gradle.kts
file.
plugins { kotlin("jvm") version "1.8.0" id("io.ktor.plugin") version "2.2.3" id("org.jetbrains.kotlin.plugin.serialization") version "1.8.0" } ... dependencies { implementation("io.ktor:ktor-server-core-jvm:$ktor_version") implementation("io.ktor:ktor-server-content-negotiation:$ktor_version") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version") implementation("io.ktor:ktor-server-netty-jvm:$ktor_version") implementation("ch.qos.logback:logback-classic:$logback_version") testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version") testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version") }
Once you have changed your gradle file, make sure to run the gradle build task to have all dependencies installed.
Now we must adapt our Application.kt
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import plugins.configureRouting
import plugins.contentNegotiation
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
.start(wait = true)
}
@Suppress("unused")
fun Application.module() {
configureRouting()
contentNegotiation()
}
In the Application module function, we will call a new extension function called contentNegotiation
. This function is implemented in the file Serialization.kt
. It defines to use of JSON as content media and adds some modifiers to how the JSON is sent. Note that in production code you want to have the most compact JSON possible.
package plugins
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import kotlinx.serialization.json.Json
fun Application.contentNegotiation() {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
})
}
}
The current folder structure can be seen in the following image. You should try to build and run the current state of your application.
Add and configure Endpoints
In the next step, we will add special API endpoints for 2 resources: Orders and Restaurants. These resources are fictive, but in a later step, we will add actual objects to these resources.
The code will be structured in such a way, that endpoints related to one resource are grouped together in one file. These files will be put in extra packages. This allows us to easily add new endpoint versions. Each of the files will add an extension function to the routing plugin.
package plugins
import io.ktor.server.routing.*
import io.ktor.server.application.*
import routing.orders.*
import routing.restaurants.*
fun Application.configureRouting() {
routing {
addOrderRoutes()
addRestaurantRoutes()
}
}
For each of the main route we will add endpoints for GET, POST, PUT and DELETE.
package routing.orders
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.addOrderRoutes () {
route("api/v1/orders") {
get() {
call.respondText { "GET api/v1/orders" }
}
get("{id}") {
call.respondText { "GET api/v1/orders/:id" }
}
post() {
call.respondText { "POST api/v1/orders/:id" }
}
put("{id}") {
call.respondText { "PUT api/v1/orders/:id" }
}
delete("{id}") {
call.respondText { "DELETE api/v1/orders/:id" }
}
}
}
package routing.restaurants
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Route.addRestaurantRoutes () {
route("api/v1/restaurants") {
get() {
call.respondText { "GET api/v1/restaurants" }
}
get("{id}") {
call.respondText { "GET api/v1/restaurants/:id" }
}
post() {
call.respondText { "POST api/v1/restaurants/:id" }
}
put("{id}") {
call.respondText { "PUT api/v1/restaurants/:id" }
}
delete ("{id}") {
call.respondText { "DELETE api/v1/restaurants/:id" }
}
}
}
You can test these endpoints with your browser or with postman as we have shown below. At this point, our endpoints are not doing anything. But we can show that client API requests are correctly routed to our application interface.