Как написать REST API на Kotlin за 30 минут: на практике
Kotlin — современный язык программирования, который стремительно набирает популярность в backend-разработке. Благодаря лаконичному синтаксису, полной совместимости с Java и мощным инструментам, таким как фреймворк Ktor, создание REST API становится быстрым и приятным процессом. В этой статье мы на практике покажем, как написать простой, но полноценный REST API на Kotlin буквально за полчаса. Вы узнаете, как настроить проект, создать эндпоинты, обрабатывать JSON и запустить сервер.
Статья предназначена для разработчиков, знакомых с основами Kotlin, но не требует глубоких знаний backend-разработки. Весь код будет сопровождаться подробными пояснениями.
1. Настройка проекта и добавление зависимостей
Для начала нам потребуется создать новый проект Kotlin. Мы будем использовать Gradle — самую популярную систему сборки для Kotlin. Если вы используете IntelliJ IDEA, выберите «New Project» -> «Kotlin» -> «JVM | IDEA». В противном случае создайте папку проекта вручную.
Первым делом откройте файл build.gradle.kts и добавьте следующие зависимости для Ktor и сериализации JSON:
plugins {
kotlin("jvm") version "1.9.22"
kotlin("plugin.serialization") 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("ch.qos.logback:logback-classic:1.4.14")
}
application {
mainClass.set("ApplicationKt")
}
После синхронизации Gradle мы готовы к написанию кода. Обратите внимание, что мы используем плагин kotlinx.serialization для работы с JSON — это стандартный способ в Kotlin.
2. Создание модели данных и основного файла сервера
Для REST API нам нужна модель данных. Пусть это будет простой список задач (ToDo). Создайте файл Todo.kt:
import kotlinx.serialization.Serializable
@Serializable
data class Todo(
val id: Int,
val title: String,
val completed: Boolean = false
)
Теперь создадим основной файл Application.kt (или Main.kt). В нем мы настроим сервер и запустим его:
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.application.*
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, host = "0.0.0.0") {
configureSerialization()
configureRouting()
}.start(wait = true)
}
fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
}
}
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello, REST API on Kotlin!")
}
}
}
Этот код запускает сервер на порту 8080. Мы подключили модуль ContentNegotiation для автоматической сериализации/десериализации JSON. Запустите приложение — вы должны увидеть в браузере по адресу http://localhost:8080 приветственное сообщение.
3. Реализация CRUD-эндпоинтов (Create, Read, Update, Delete)
Теперь давайте добавим полноценные REST маршруты для управления задачами. Мы будем использовать список mutableListOf для хранения данных в памяти (в реальном проекте вы замените его на базу данных).
Создадим файл TodoRepository.kt:
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class TodoRepository {
private val todos = mutableListOf(
Todo(1, "Изучить Kotlin"),
Todo(2, "Написать REST API")
)
private var nextId = 3
private val mutex = Mutex()
suspend fun getAll(): List<Todo> = mutex.withLock { todos.toList() }
suspend fun getById(id: Int): Todo? = mutex.withLock { todos.find { it.id == id } }
suspend fun create(todo: Todo): Todo = mutex.withLock {
val newTodo = todo.copy(id = nextId++)
todos.add(newTodo)
newTodo
}
suspend fun update(id: Int, todo: Todo): Todo? = mutex.withLock {
val index = todos.indexOfFirst { it.id == id }
if (index != -1) {
val updated = todo.copy(id = id)
todos[index] = updated
updated
} else null
}
suspend fun delete(id: Int): Boolean = mutex.withLock {
todos.removeAll { it.id == id }
}
}
Обратите внимание на использование Mutex — это обеспечивает потокобезопасность, так как Ktor работает с корутинами.
Теперь обновим функцию configureRouting() в Application.kt:
fun Application.configureRouting() {
val repository = TodoRepository()
routing {
get("/todos") {
val todos = repository.getAll()
call.respond(todos)
}
get("/todos/{id}") {
val id = call.parameters["id"]?.toIntOrNull() ?: return@get call.respondText(
"Invalid id", status = io.ktor.http.HttpStatusCode.BadRequest
)
val todo = repository.getById(id)
if (todo != null) {
call.respond(todo)
} else {
call.respondText("Not Found", status = io.ktor.http.HttpStatusCode.NotFound)
}
}
post("/todos") {
val todo = call.receive<Todo>()
val created = repository.create(todo)
call.respond(io.ktor.http.HttpStatusCode.Created, created)
}
put("/todos/{id}") {
val id = call.parameters["id"]?.toIntOrNull() ?: return@put call.respondText(
"Invalid id", status = io.ktor.http.HttpStatusCode.BadRequest
)
val todo = call.receive<Todo>()
val updated = repository.update(id, todo)
if (updated != null) {
call.respond(updated)
} else {
call.respondText("Not Found", status = io.ktor.http.HttpStatusCode.NotFound)
}
}
delete("/todos/{id}") {
val id = call.parameters["id"]?.toIntOrNull() ?: return@delete call.respondText(
"Invalid id", status = io.ktor