Как написать REST API на Kotlin за 30 минут: для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение

Kotlin — это современный статически типизированный язык программирования, который работает на виртуальной машине Java (JVM) и полностью совместим с Java-экосистемой. Благодаря своей лаконичности, безопасности и мощным функциональным возможностям, Kotlin стал одним из самых популярных языков для разработки серверных приложений, особенно после того, как Google выбрал его в качестве основного языка для Android.

В этой статье мы создадим полноценное REST API на Kotlin, используя легковесный фреймворк Ktor и библиотеку для работы с базами данных Exposed. Мы реализуем простой сервис для управления задачами (To-Do List) с поддержкой всех CRUD-операций (создание, чтение, обновление, удаление). Весь процесс займет у вас не более 30 минут, если вы уже установили IntelliJ IDEA и JDK.

1. Настройка проекта

Для начала создадим новый проект Kotlin с поддержкой Gradle. Откройте IntelliJ IDEA и выберите File → New → Project. В мастере создания проекта выберите Kotlin → JVM.

Шаг 1.1: Структура build.gradle.kts

Добавьте следующие зависимости в файл build.gradle.kts:

plugins {
    kotlin("jvm") version "1.9.0"
    id("io.ktor.plugin") version "2.3.0"
    kotlin("plugin.serialization") version "1.9.0"
}

dependencies {
    implementation("io.ktor:ktor-server-core:2.3.0")
    implementation("io.ktor:ktor-server-netty:2.3.0")
    implementation("io.ktor:ktor-server-content-negotiation:2.3.0")
    implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.0")
    implementation("org.jetbrains.exposed:exposed-core:0.41.1")
    implementation("org.jetbrains.exposed:exposed-dao:0.41.1")
    implementation("org.jetbrains.exposed:exposed-jdbc:0.41.1")
    implementation("com.h2database:h2:2.1.214")
    implementation("ch.qos.logback:logback-classic:1.4.7")
}

Эти зависимости включают: сам фреймворк Ktor, поддержку JSON-сериализации, библиотеку Exposed для работы с БД и встроенную базу данных H2 для тестирования.

Шаг 1.2: Точка входа

Создайте файл Application.kt в пакете com.example:

import io.ktor.server.engine.*
import io.ktor.server.netty.*
import com.example.plugins.*

fun main() {
    embeddedServer(Netty, port = 8080) {
        configureSerialization()
        configureRouting()
    }.start(wait = true)
}

2. Настройка сериализации и маршрутизации

Шаг 2.1: Плагины приложения

Создайте директорию plugins и файл Serialization.kt:

package com.example.plugins

import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json()
    }
}

Шаг 2.2: Определение модели данных

Создайте файл Models.kt:

package com.example.models

import kotlinx.serialization.Serializable

@Serializable
data class Task(
    val id: Int = 0,
    val title: String,
    val description: String = "",
    val completed: Boolean = false
)

3. Работа с базой данных

Шаг 3.1: Настройка подключения

Создайте файл DatabaseFactory.kt:

package com.example.database

import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction

object DatabaseFactory {
    fun init() {
        Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver")
        transaction {
            SchemaUtils.create(TasksTable)
        }
    }
}

Шаг 3.2: Определение таблицы

В том же пакете создайте TasksTable.kt:

package com.example.database

import org.jetbrains.exposed.sql.Table

object TasksTable : Table("tasks") {
    val id = integer("id").autoIncrement()
    val title = varchar("title", 255)
    val description = varchar("description", 1024).default("")
    val completed = bool("completed").default(false)

    override val primaryKey = PrimaryKey(id)
}

4. Реализация CRUD-маршрутов

Теперь самое интересное — создадим обработчики запросов. Отредактируйте файл Routing.kt в пакете plugins:

package com.example.plugins

import com.example.database.DatabaseFactory
import com.example.database.TasksTable
import com.example.models.Task
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction

fun Application.configureRouting() {
    DatabaseFactory.init()

    routing {
        route("/api/tasks") {
            // GET: Получить все задачи
            get {
                val tasks = transaction {
                    TasksTable.selectAll().map { row ->
                        Task(
                            id = row[TasksTable.id],
                            title = row[TasksTable.title],
                            description = row[TasksTable.description],
                            completed = row[TasksTable.completed]
                        )
                    }
                }
                call.respond(tasks)
            }

            // GET: Получить задачу по ID
            get("{id}") {
                val id = call.parameters["id"]?.toIntOrNull()
                if (id == null) {
                    call.respond(HttpStatusCode.BadRequest, "Invalid ID")
                    return@get
                }
                val task = transaction {
                    TasksTable.selectAll().where { TasksTable.id eq id }
                        .singleOrNull()?.let {
                            Task(it[TasksTable.id], it[TasksTable.title], it[TasksTable.description], it[TasksTable.completed])
                        }
                }
                if (task != null) call.respond(task)
                else call.respond(HttpStatusCode.NotFound, "Task not found")
            }

            // POST: Создать новую задачу
            post {
                val task = call.receive<Task>()
                val id = transaction {
                    TasksTable.insert {
                        it[title] = task.title
                        it[description] = task.description
                        it[completed] =                

Блоги

Рекомендаций по книгам