Introduction
Kotlin is a modern programming language that is rapidly gaining popularity in backend development. Thanks to its concise syntax, full compatibility with Java, and powerful tools such as the Ktor framework, writing a REST API in Kotlin takes literally 30 minutes. In this article, we will create a full-featured REST API for managing a task list (Todo API). You will learn how to set up a project, create endpoints, connect a database, and test the API. No unnecessary theory — just practice.
Step 1: Project setup and dependencies
For a quick start, we use IntelliJ IDEA (Community Edition) or any other editor. Let's create a new Gradle project with Kotlin DSL.
Creating the project structure
Add the following dependencies to the build.gradle.kts file:
plugins { kotlin("jvm") version "1.9.22" application}
repositories { mavenCentral()}
dependencies { implementation("io.ktor:ktor-server-core:2.3.7") implementation("io.ktor:ktor-server-netty:2.3.7") implementation("io.ktor:ktor-server-content-negotiation:2.3.7") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7") implementation("org.jetbrains.exposed:exposed-core:0.44.1") implementation("org.jetbrains.exposed:exposed-dao:0.44.1") implementation("org.jetbrains.exposed:exposed-jdbc:0.44.1") implementation("com.h2database:h2:2.2.224") implementation("ch.qos.logback:logback-classic:1.4.14")}
application { mainClass.set("ApplicationKt")}We are using:
- Ktor — a lightweight asynchronous framework for HTTP servers.
- Exposed — an ORM for working with databases.
- H2 — an embedded database for development.
- Kotlinx.serialization — for working with JSON.
Step 2: Creating the data model and connecting the database
Let's define the structure of the tasks table. Create the file Database.kt:
import org.jetbrains.exposed.sql.Tableimport org.jetbrains.exposed.sql.insertimport org.jetbrains.exposed.sql.selectAllimport org.jetbrains.exposed.sql.transactions.transaction
object Tasks : Table() { val id = integer("id").autoIncrement() val title = varchar("title", 255) val completed = bool("completed").default(false)
override val primaryKey = PrimaryKey(id)}
data class Task(val id: Int, val title: String, val completed: Boolean)
fun initDatabase() { Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") transaction { SchemaUtils.create(Tasks) }}This code creates the Tasks table with three fields: id, title, and completed. The initDatabase() function initializes the in-memory database.
Step 3: Writing REST API controllers
Now let's create the main application file Application.kt. Here we will define all the endpoints.
Basic server setup
import io.ktor.server.application.*import io.ktor.server.engine.*import io.ktor.server.netty.*import io.ktor.server.routing.*import io.ktor.server.response.*import io.ktor.server.request.*import io.ktor.serialization.kotlinx.json.*import io.ktor.server.plugins.contentnegotiation.*
fun main() { embeddedServer(Netty, port = 8080) { install(ContentNegotiation) { json() } initDatabase() configureRouting() }.start(wait = true)}Implementing CRUD endpoints
fun Application.configureRouting() { routing { // Get all tasks get("/tasks") { val tasks = transaction { Tasks.selectAll().map { row -> Task( id = row[Tasks.id],