Learn Kotlin - Kotlin for Backend Development
Series/Learn Kotlin/Episode 11
Episode 11 of 23

Learn Kotlin - Kotlin for Backend Development

This episode builds a backend with Kotlin: choosing between Ktor, Spring Boot, and Micronaut, routing and request handling, JSON serialization, dependency injection and configuration, and writing a REST API with a complete middleware setup.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

After mastering Android, episode 11 points Kotlin at another equally popular direction: backend development. Kotlin has become a leading choice for modern server services thanks to its combination of type safety, coroutines, and interoperability with the entire JVM ecosystem.

There are three main frameworks: Ktor, lightweight and idiomatic Kotlin; Spring Boot, with the largest ecosystem; and Micronaut, fast to start up and memory-efficient. This episode builds a REST API with Ktor, then touches on the same patterns in the other frameworks.

By the end of this episode you'll run a simple API that handles requests, performs serialization, and injects dependencies.

Choosing a Framework

Ktor: The JetBrains Framework

Ktor was built by JetBrains specifically for Kotlin: it fully uses suspending functions and is idiomatic. It's lightweight and flexible — you enable features as you need them. Start a project with the Gradle plugin:

Scaffold project Ktor
curl -L -o ktor.zip https://start.ktor.io/zip/ktor-server-netty
unzip ktor.zip -d app-api
cd app-api
./gradlew run

https://start.ktor.io is Ktor's official project generator. The command ./gradlew run starts the server on the default port 8080. Ktor is ideal for small APIs, microservices, and projects that want minimal magic.

Spring Boot and Micronaut

Spring Boot provides the largest ecosystem: Spring Data, Spring Security, and much more, with first-class Kotlin support. Micronaut stands out for fast startup and a small footprint because dependency injection and AOP are compiled at build time rather than evaluated at runtime. Choose Spring Boot for ecosystem maturity, Micronaut for serverless performance.

Routing and Request Handling

Defining Routes with Ktor

Routing in Ktor is declared with a readable DSL. Each route uses a suspending function to handle the request:

KotlinRouting dasar Ktor
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
 
fun Application.module() {
    routing {
        get("/") {
            call.respondText("Selamat datang di API")
        }
        get("/pengguna/{id}") {
            val id = call.parameters["id"]
            call.respond(mapOf("id" to id))
        }
    }
}

get("/pengguna/{id}") captures a path parameter and reads it via call.parameters["id"]. Every handler runs as a coroutine, so delay or I/O calls do not block the server thread.

HTTP Methods and Status

Ktor provides helpers for every HTTP method: get, post, put, delete, and patch. Responses can be text, JSON, or files. Sending the correct status and headers is part of the REST contract we'll complete in the serialization section.

JSON Serialization

Enabling Content Negotiation

To send and receive JSON, enable ContentNegotiation with kotlinx.serialization:

KotlinContent negotiation
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
 
install(ContentNegotiation) {
    json(Json { ignoreUnknownKeys = true })
}
 
@Serializable
data class RequestBuat(val judul: String, val isi: String)

The RequestBuat class annotated @Serializable is automatically mapped to and from JSON request bodies. The ignoreUnknownKeys = true option makes the API tolerant of extra fields from clients.

Receiving and Returning JSON

A POST handler reads the body with call.receive:

KotlinMenerima request JSON
post("/artikel") {
    val req = call.receive<RequestBuat>()
    val artikel = simpan(req)
    call.respond(artikel)
}

call.receive<RequestBuat>() maps the JSON body to an object, and call.respond(artikel) sends the object back as JSON. The entire serialization and deserialization process is handled by the framework.

Dependency Injection and Configuration

Manual Injection and Lightweight Injection

For a small Ktor API, dependency injection can be done manually by building services at application startup. For larger scale, use a library like Koin or a framework with built-in DI:

KotlinService sederhana
class ArtikelRepository {
    private val items = mutableListOf<RequestBuat>()
 
    fun simpan(a: RequestBuat): RequestBuat {
        items.add(a)
        return a
    }
}

Wrapping repositories and services in constructors gives you the same testability as a DI framework, without extra dependencies. As your needs grow, Koin or Spring DI replaces this manual pattern seamlessly.

Configuration with the Environment

Configuration such as port and database credentials is read from the environment:

Environment config
export PORT=8080
export DATABASE_URL=jdbc:postgresql://localhost:5432/app
./gradlew run

The command export PORT=8080 sets an environment variable the app reads at startup. Binding configuration to the environment — not hardcoding it — is a required practice for deploying the same app across several environments, as discussed in episode 19.

Middleware and a Complete REST API

Middleware for Logging and Validation

Ktor uses plugins as middleware. Custom middleware can record request time and measure latency:

KotlinMiddleware logging
fun Application.installMonitoring() {
    intercept(ApplicationCallPipeline.Monitoring) {
        val mulai = System.currentTimeMillis()
        proceed()
        val selesai = System.currentTimeMillis()
        call.application.environment.log.info(
            "Lama request: ${selesai - mulai} ms"
        )
    }
}

intercept inserts logic before and after request processing. proceed() continues to the next handler. Middleware like this is the right place for logging, authentication, and rate limiting — layered in the pipeline.

Closing

Episode 11 built a backend with Kotlin: choosing between Ktor, Spring Boot, or Micronaut, defining routes with the Ktor DSL, handling JSON via ContentNegotiation, managing dependencies and configuration, and assembling middleware and a complete REST API.

The key takeaways:

  • Ktor is idiomatic Kotlin; Spring Boot has a mature ecosystem; Micronaut starts fast.
  • Ktor routing uses suspending functions, so handlers don't block threads.
  • ContentNegotiation with kotlinx.serialization handles JSON automatically.
  • call.receive for the request body, call.respond for the response.
  • Configuration is bound to the environment, not hardcoded.
  • Middleware via intercept for logging, authentication, and validation.

In episode 12 we'll discuss Kotlin multiplatform and cross-platform — the Kotlin Multiplatform architecture, a shared module for JVM, Android, JS, and Native, expect and actual declarations, and use cases for mobile, desktop, and web.

Learn Kotlin - Kotlin for Backend Development | Learn Kotlin