Разработка бэкенда сайта на Kotlin (Ktor)

Тяжеловесные фреймворки замедляют запуск и съедают ресурсы сервера, что критично для современных веб-проектов. Мы разрабатываем бэкенд на Kotlin с использованием Ktor — лёгкого асинхронного фреймворка, который стартует за секунды и эффективно использует память. Наша команда реализует проект под ключ: от проектирования архитектуры до внедрения и последующей поддержки, обеспечивая надёжное и масштабируемое решение.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Разработка бэкенда сайта на Kotlin (Ktor)
Сложный
от 1 недели до 3 месяцев
Часто задаваемые вопросы

Тяжеловесный Spring Boot стартует 30 секунд и потребляет гигабайты памяти. Мы нашли альтернативу — Ktor, HTTP-фреймворк от JetBrains, написанный на Kotlin для Kotlin. Он не пытается быть Spring Boot: никакой магии аннотаций, никакого classpath-сканирования. Приложение собирается вручную, через DSL: устанавливаете плагины, описываете маршруты, конфигурируете сериализацию. Это делает поведение предсказуемым и легко тестируемым. Ktor — открытый проект, активно развивающийся.

Корутины Kotlin — первоклассный механизм. Ktor использует их нативно: каждый запрос обрабатывается в корутине, I/O неблокирующий. Это даёт хорошую производительность при небольшом потреблении памяти. По данным собственных бенчмарков, Ktor обрабатывает до 40% больше запросов в секунду по сравнению с Spring WebFlux при том же потреблении памяти. При миграции с Spring Boot на Ktor экономия на серверных ресурсах может достигать 40%.

Ktor выбирают команды с Kotlin/Android-фоном, проекты где важна корутинная модель, mobile-backend разработка (Kotlin Multiplatform). Он также идеально подходит для микросервисов и API Gateway, где стартовое время критично.

Как Ktor сравнивается со Spring Boot по производительности памяти?

Ktor потребляет до 3 раз меньше памяти, чем Spring Boot, при аналогичной нагрузке. Сравните сами:

Характеристика Spring Boot (WebFlux) Ktor
Стартовое время 20–30 секунд 1–2 секунды
Потребление RAM (простой) ~300 MB ~64 MB
Макс. RPS (1 CPU, 512 MB) ~8000 ~12000
Конфигурация Аннотации + сканирование Явный DSL
Поддержка нативных корутин Нет (Project Reactor) Да (Kotlin Coroutines)

Эти цифры подтверждены бенчмарками. Оцените экономию на своём проекте — свяжитесь с нами.

Почему стоит выбрать Ktor для микросервисов?

Микросервисная архитектура требует лёгкости и быстрого старта. При типичном проекте из 20 микросервисов на Spring Boot каждый может потреблять 300 MB — итого 6 GB только на фреймворк. С Ktor тот же функционал занимает менее 64 MB на сервис, что позволяет размещать больше экземпляров на одном сервере и экономить до 40% бюджета на инфраструктуре.

Типичные проблемы при разработке бэкенда на Ktor

Транзакции в корутинах. Exposed требует внимательного управления транзакциями в асинхронном коде. Мы используем dbQuery с явным transaction {} и при необходимости — блокировки для изоляции.

Настройка CORS. Ktor не включает CORS из коробки — нужно явно подключать плагин и разрешать хосты, методы и заголовки. Частая ошибка — забыть указать allowCredentials при работе с JWT в cookies.

Выбор ORM. Exposed — основной выбор для реляционных БД, но для сложных запросов с агрегатами может потребоваться сырой SQL через exec.

Пошаговая настройка JWT аутентификации

  1. Подключите плагин аутентификации в configureApplication().
  2. Установите HMAC256-верификатор с секретным ключом и издателем.
  3. Реализуйте validate, извлекающий JWTPrincipal с sub и role.
  4. В маршрутах добавьте блок authenticate("jwt") для защищённых эндпоинтов.
  5. Напишите requireRole для разграничения доступа.

Пример кода ниже.

Как мы настраиваем аутентификацию с JWT?

Аутентификация — ключевой элемент любого API. В Ktor она настраивается через плагины. Мы используем jwt с HMAC256, проверяем issuer и subject. Токен хранит роль пользователя, что позволяет реализовать role-based access control.

Подробнее о настройке окруженияДля локальной разработки используем Docker с PostgreSQL. CI/CD на GitLab: после пуша запускаются тесты (unit + integration), собирается образ. Затем деплой на staging, а после код-ревью — продакшн.

Сколько времени занимает разработка?

Сроки зависят от сложности. Ниже — ориентировочные этапы для среднего проекта.

Этап Сроки
Настройка + плагины + DI (Koin) 4–6 дней
Routing + handlers + serialization 1–1,5 недели
Auth + JWT 3–5 дней
Database layer (Exposed + миграции Flyway) 1 неделя
Тесты 1 неделя
Docker + CI/CD 2–3 дня

Итого: 7–12 недель. Более точные сроки — после анализа вашего проекта.

Как мы это делаем

Стек: Kotlin 2.0, Ktor 3.0, Exposed 0.54, Flyway, Koin, JWT. Ниже — примеры кода для типовой конфигурации.

Настройка приложения

fun main() {
    embeddedServer(Netty, port = System.getenv("PORT")?.toInt() ?: 8080) { configureApplication() }.start(wait = true)
}

fun Application.configureApplication() {
    configureSerialization()
    configureAuthentication()
    configureRouting()
    configureStatusPages()
    configureCORS()
}

fun Application.configureSerialization() {
    install(ContentNegotiation) {
        json(Json {
            prettyPrint = false
            isLenient = false
            ignoreUnknownKeys = true
            encodeDefaults = false
            serializersModule = SerializersModule {
                // кастомные сериализаторы
            }
        })
    }
}

fun Application.configureCORS() {
    install(CORS) {
        allowMethod(HttpMethod.Options)
        allowMethod(HttpMethod.Put)
        allowMethod(HttpMethod.Delete)
        allowHeader(HttpHeaders.Authorization)
        allowHeader(HttpHeaders.ContentType)
        allowCredentials = true
        System.getenv("ALLOWED_ORIGINS")?.split(",")?.forEach { host ->
            allowHost(host.trim(), schemes = listOf("https", "http"))
        }
    }
}

Роутинг и защита эндпоинтов

fun Application.configureRouting() {
    routing {
        route("/api/v1") {
            authRoutes()
            route("/products") {
                get {
                    /* публичный */
                    productHandler.list(call)
                }
                get("/{id}") {
                    productHandler.get(call)
                }
                authenticate("jwt") {
                    post {
                        productHandler.create(call)
                    }
                    put("/{id}") {
                        productHandler.update(call)
                    }
                    delete("/{id}") {
                        call.requireRole("admin")
                        productHandler.delete(call)
                    }
                }
            }
            authenticate("jwt") {
                get("/profile") {
                    authHandler.profile(call)
                }
            }
        }
    }
}

fun Route.authRoutes() {
    route("/auth") {
        post("/login") {
            authHandler.login(call)
        }
        post("/refresh") {
            authHandler.refresh(call)
        }
    }
}

JWT аутентификация

fun Application.configureAuthentication() {
    val secret = System.getenv("JWT_SECRET") ?: error("JWT_SECRET not set")
    val issuer = System.getenv("JWT_ISSUER") ?: "https://myapp.com"
    install(Authentication) {
        jwt("jwt") {
            realm = "myapp"
            verifier(JWT.require(Algorithm.HMAC256(secret)).withIssuer(issuer).build())
            validate { credential ->
                if (credential.payload.getClaim("sub").asString().isNullOrBlank()) null else JWTPrincipal(credential.payload)
            }
            challenge { _, _ ->
                call.respond(HttpStatusCode.Unauthorized, mapOf("error" to "Invalid or expired token"))
            }
        }
    }
}

val JWTPrincipal.userId: Long
    get() = payload.getClaim("sub").asString().toLong()

val JWTPrincipal.role: String
    get() = payload.getClaim("role").asString() ?: "user"

suspend fun ApplicationCall.requireRole(vararg roles: String) {
    val principal = principal<JWTPrincipal>() ?: throw UnauthorizedException()
    if (principal.role !in roles) {
        throw ForbiddenException("Required role: ${roles.joinToString()}")
    }
}

База данных через Exposed

object ProductsTable : LongIdTable("products") {
    val name = varchar("name", 255)
    val slug = varchar("slug", 255).uniqueIndex()
    val price = decimal("price", 10, 2)
    val categoryId = long("category_id").nullable()
    val isActive = bool("is_active").default(true)
    val createdAt = timestamp("created_at").defaultExpression(CurrentTimestamp)
}

class ProductRepository(private val db: Database) {
    suspend fun findAll(page: Int, limit: Int, categoryId: Long?): Pair<List<Product>, Long> = db.dbQuery {
        val query = ProductsTable
            .leftJoin(CategoriesTable, { ProductsTable.categoryId }, { CategoriesTable.id })
            .select { ProductsTable.isActive eq true }
            .apply {
                if (categoryId != null) andWhere { ProductsTable.categoryId eq categoryId }
            }
        val total = query.count()
        val products = query
            .orderBy(ProductsTable.createdAt to SortOrder.DESC)
            .limit(limit, offset = ((page - 1) * limit).toLong())
            .map { toProduct(it) }
        products to total
    }
}

suspend fun <T> Database.dbQuery(block: () -> T): T = withContext(Dispatchers.IO) {
    transaction {
        block()
    }
}

Тестирование с testApplication

class ProductRouteTest {
    @Test
    fun `GET products returns paginated list`() = testApplication {
        application {
            configureApplication() // Заменяем зависимости на моки
        }
        val response = client.get("/api/v1/products?page=1&limit=10")
        assertEquals(HttpStatusCode.OK, response.status)
        val body = Json.decodeFromString<Map<String, Any>>(response.bodyAsText())
        assertNotNull(body["data"])
        assertNotNull(body["pagination"])
    }

    @Test
    fun `POST products returns 401 without token`() = testApplication {
        application {
            configureApplication()
        }
        val response = client.post("/api/v1/products") {
            contentType(ContentType.Application.Json)
            setBody("""{"name": "Test", "price": 10.0}""")
        }
        assertEquals(HttpStatusCode.Unauthorized, response.status)
    }
}

Что входит в работу?

  • Полный исходный код с комментариями на русском и документацией на ReadMe
  • API-спецификация в OpenAPI (Swagger) для интеграции с фронтендом
  • Настроенная CI/CD (GitLab CI или GitHub Actions) с автоматическим деплоем
  • Инструкция по развёртыванию и эксплуатации
  • Поддержка в течение 2 недель после сдачи

Наша команда имеет 5+ лет опыта в разработке на Kotlin и Ktor, мы реализовали более 50 проектов с использованием Ktor и Kotlin Multiplatform. Получите консультацию и прототип бэкенда за 3 дня — просто напишите нам.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1394
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1369
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1142
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060