Node.js та Koa: повний цикл бекенду — middleware, валідація, деплой
Koa — мінімалістичний фреймворк від творців Express, переосмислений під async/await. Там де Express вимагає next() і колбеків, Koa працює через async/await і стек middleware, який виконується за принципом «цибулі»: запит проходить middleware зверху вниз, потім відповідь — знизу вгору. Це принципова відмінність: після await next() ви повертаєтеся назад у middleware з доступом до фінального стану відповіді. Вибирають Koa тоді, коли потрібна повна свобода вибору бібліотек без думок фреймворку, але з нормальною обробкою async-коду на відміну від Express.
Ми використовуємо Koa для проектів, де важлива продуктивність і мінімальний оверхед — це підтверджено досвідом розробки понад 50 API, на одному з яких ми обробляли до 10 000 запитів на секунду на одному інстансі. Оптимізація інфраструктури дозволяє знизити споживання пам'яті на 35%, що дає економію на серверній інфраструктурі до 35% щомісячних витрат. Терміни розробки розраховуються індивідуально.
Як middleware розв'язує типові проблеми Express?
import Koa from 'koa'
import Router from '@koa/router'
const app = new Koa()
app.use(async (ctx, next) => {
const start = Date.now()
await next()
const ms = Date.now() - start
console.log(`${ctx.method} ${ctx.url} - ${ctx.status} - ${ms}ms`)
})
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
ctx.status = err.statusCode || err.status || 500
ctx.body = {
error: process.env.NODE_ENV === 'production' ? 'Internal Server Error' : err.message
}
ctx.app.emit('error', err, ctx)
}
})
Цей патерн — основа onion-архітектури. Спробуйте повторити таке в Express без зовнішніх бібліотек: доведеться писати костилі. Koa дає це з коробки. Стек middleware дозволяє реалізувати наскрізну обробку помилок, логування та авторизації без дублювання коду.
Обробка помилок в Koa
Обробка помилок в Koa будується на ланцюжку middleware. Приклад вище показує, як єдиний обробник може перехоплювати будь-які винятки. Додатково можна слухати подію app.on('error', ...) для централізованого логування. Це дозволяє уникнути дублювання коду та гарантує, що кожна помилка буде коректно замаскована в продакшені.
Валідація та аутентифікація без зайвого коду
Валідація через Zod
Koa не включає валідацію — підключаємо Zod:
import { z } from 'zod'
const createProductSchema = z.object({
name: z.string().min(2).max(255),
price: z.number().positive(),
categoryId: z.number().int().positive(),
description: z.string().optional(),
attributes: z.record(z.unknown()).optional()
})
const validateBody = (schema) => async (ctx, next) => {
const result = schema.safeParse(ctx.request.body)
if (!result.success) {
ctx.status = 422
ctx.body = { errors: result.error.flatten().fieldErrors }
return
}
ctx.validatedBody = result.data
await next()
}
router.post('/products',
authenticate,
validateBody(createProductSchema),
async (ctx) => {
const product = await ProductService.create(ctx.validatedBody)
ctx.status = 201
ctx.body = product
}
)
Така фабрика middleware дає типізовану та безпечну валідацію без прив'язки до конкретного фреймворку. У комбінації з TypeScript ви отримуєте повний контроль над типами.
JWT аутентифікація
@koa/router — офіційний роутер. Налаштування JWT через koa-jwt або вручну:
import jwt from 'jsonwebtoken'
const authenticate = async (ctx, next) => {
const authHeader = ctx.headers.authorization
if (!authHeader?.startsWith('Bearer ')) {
ctx.throw(401, 'No token provided')
}
try {
const token = authHeader.slice(7)
ctx.state.user = jwt.verify(token, process.env.JWT_SECRET)
await next()
} catch {
ctx.throw(401, 'Invalid or expired token')
}
}
Сесії через koa-session + Redis store — ще один поширений сценарій. Час життя сесії налаштовується, рекомендуємо 7 днів для користувацьких сесій.
Чому Koa швидший за Express і коли він не потрібен?
Виграш у продуктивності
Koa написаний з нуля на генераторах та async/await, його ядро важить менше 600 рядків коду. Це безпосередньо впливає на TTFB і дозволяє легко налаштовувати кожен middleware. На відміну від Express, у Koa немає вбудованих допоміжних функцій (наприклад, res.json()), що знижує оверхед. Бенчмарки показують, що Koa обробляє на 15-20% більше запитів на секунду при однаковому навантаженні. Зниження споживання пам'яті сягає 35%, що дозволяє скоротити кількість серверів і заощадити до 30% бюджету на інфраструктуру.
| Параметр | Koa | Express | Fastify |
|---|---|---|---|
| Середній час відповіді (ms) | 2.1 | 2.8 | 1.9 |
| Споживання пам'яті (MB) | 12 | 18 | 14 |
| Кількість middleware | 3 | 5 | 2 |
Коли краще вибрати Fastify або NestJS
Koa дає мінімальний оверхед — його ядро важить менше 600 рядків коду. Це безпосередньо впливає на TTFB і дозволяє легко налаштовувати кожен middleware. У поєднанні з TypeScript та сучасними практиками (Repository pattern, BFF) ви отримуєте швидкий та передбачуваний бекенд. Ми гарантуємо стабільну роботу API навіть при високих навантаженнях.
Однак Koa вимагає самостійного збирання: немає вбудованої валідації, немає swagger-генерації, немає DI. Якщо проект зростає і потрібна структура — краще Fastify (продуктивність + схеми) або NestJS (архітектура). Koa залишається актуальним для невеликих API, проксі-серверів і проектів, де команда хоче повний контроль без фреймворк-магії.
Практична структура та процес
Приклад структури проекту
src/
index.js # точка входу
app.js # створення koa-додатку
middleware/
auth.js
errorHandler.js
requestLogger.js
validate.js
routes/
index.js
products.js
users.js
orders.js
services/
products.js
users.js
models/
config/
utils/
Розділення на routes, services, models — класичний підхід. Детальніше про Repository pattern описано в документації Microsoft.
Завантаження файлів
@koa/multer для multipart:
import multer from '@koa/multer'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
if (!file.mimetype.startsWith('image/')) {
return cb(new Error('Only images allowed'))
}
cb(null, true)
}
})
router.post('/upload',
authenticate,
upload.single('file'),
async (ctx) => {
const file = ctx.file
const key = `uploads/${Date.now()}-${file.originalname}`
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: key,
Body: file.buffer,
ContentType: file.mimetype
}))
ctx.body = { url: `https://${process.env.CDN_HOST}/${key}` }
}
)
Обмеження розміру файлу — обов'язковий захист від DoS-атак.
Етапи розробки та орієнтири за строками
| Компонент | Інструмент | Альтернативи |
|---|---|---|
| Сервер | Koa | Fastify, Express |
| Роутинг | @koa/router | koa-router |
| Валідація | Zod | Joi, Yup |
| ORM | Prisma | TypeORM, Sequelize |
| Тестування | Jest + Supertest | Vitest, Mocha |
- Аналітика та проектування архітектури — 1–2 дні
- Налаштування стеку (роути, middleware, БД) — 3–5 днів
- Реалізація CRUD + аутентифікація — 1–2 тижні
- Інтеграції (email, файли, платіжки) — 1–2 тижні
- Тестування (jest + supertest) — 3–5 днів
- Деплой та документація — 1–2 дні
Типові помилки та їх вирішення
- N+1 запити — використовуємо DataLoader або batch-запити.
-
Відсутність лімітів за розміром тіла — налаштовуємо
koa-bodyтаmulter. - Витік пам'яті через middleware — слідкуємо за контекстом і не зберігаємо посилання на великі об'єкти.
Що входить в результат
Після завершення розробки ви отримуєте:
- Вихідний код з покриттям тестами не менше 80%
- Документацію API (OpenAPI/Swagger) при необхідності
- Доступи до сервера та репозиторію
- Інструкцію з деплою
- Гарантію на код — 3 місяці безкоштовної підтримки
Простий API для сайту-візитки або лендінгу: 3–6 тижнів. Koa швидко стартує, але вимагає акуратності в організації коду. Оцінимо ваш проект безкоштовно — зв'яжіться, щоб обговорити деталі. Замовте розробку бекенду на Koa вже сьогодні — отримайте консультацію інженера.







