Розробка бекенду на Express: від ідеї до модульної архітектури
Уявіть: сайт на монолітному PHP починає гальмувати при 10 000 запитів на хвилину. N+1 запити, відсутність кешування, час відповіді більше 2 секунд. Ви вирішуєте переписати бекенд на Node.js. Express — логічний кандидат: легкий, гнучкий, з величезним ком'юніті. Але як побудувати архітектуру, щоб витримати зростання і не потонути в спагеті-коді? Ми розберемо перевірений підхід: модульна архітектура, middleware-ланцюжки, кешування та graceful shutdown.
Які проблеми вирішує Express-бекенд?
Express залишається прагматичним вибором для бекенду: мінімум магії, передбачувана поведінка, величезна екосистема middleware. Не найшвидший фреймворк (Fastify швидше на 20–30% у бенчмарках), не найбагатший на функції (NestJS багатший), але поєднання простоти та гнучкості робить його робочим інструментом для більшості завдань. Головні проблеми, які ми вирішуємо:
- Спагеті-код — хаотична структура, коли роути, бізнес-логіка та доступ до даних змішані. Це призводить до того, що зміна одного модуля ламає інший.
- Вузькі місця продуктивності — N+1 запити, відсутність кешування, неоптимальні індекси в БД. Ми використовуємо Redis для кешування та Prisma для ефективних запитів. Наприклад, в одному проєкті TTFB знизився з 500 мс до 50 мс після впровадження кешування.
- Проблеми безпеки — невалідовані вхідні дані, вразливості JWT, відкриті CORS-політики. Валідація через Zod знижує кількість багів на 30–40%.
- Складність підтримки — відсутність єдиного стилю, тестів та документації. Кожен модуль покриваємо unit-тестами, для API — e2e-тести (Vitest, Supertest).
Як модульна архітектура вирішує проблеми масштабування?
В основі — модульна архітектура з розділенням на Router → Service → Repository. Це масштабується від лендінгу до enterprise-системи. Ось як виглядає структура типового проєкту:
src/
├── config/
│ ├── env.ts # typed env validation (zod)
│ └── database.ts
├── modules/
│ ├── users/
│ ├── products/
│ └── orders/
├── middleware/
│ ├── auth.ts
│ ├── errorHandler.ts
│ ├── requestLogger.ts
│ └── rateLimit.ts
├── lib/
│ ├── database.ts # Prisma client
│ ├── redis.ts
│ ├── mailer.ts
│ └── queue.ts
└── app.ts
Така архітектура дає чіткі межі відповідальності: роути парсять запит, сервіси містять бізнес-логіку, репозиторії працюють з даними. Порівняйте з плоскою структурою:
| Аспект | Плоска структура | Модульна архітектура |
|---|---|---|
| Масштабування | Важко | Легко (додаємо модулі) |
| Тестування | Хаотичне | Ізольоване (mock репозиторіїв) |
| Перевикористання | Низьке | Високе (сервіси незалежні) |
| Розуміння коду | Тільки автором | Командне |
Приклад модуля: Products
// modules/products/products.service.ts
import { ProductsRepository } from './products.repository';
import { redis } from '../../lib/redis';
export class ProductsService {
private repo = new ProductsRepository();
async list(query: ListProductsQuery) {
const cacheKey = `products:list:${JSON.stringify(query)}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const result = await this.repo.findMany(query);
await redis.set(cacheKey, JSON.stringify(result), 'EX', 300);
return result;
}
async getById(id: string) {
const cacheKey = `products:${id}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const product = await this.repo.findById(id);
if (product) await redis.set(cacheKey, JSON.stringify(product), 'EX', 3600);
return product;
}
async create(data: CreateProductDto, createdBy: string) {
const product = await this.repo.create({ ...data, createdBy });
const keys = await redis.keys('products:list:*');
if (keys.length > 0) await redis.del(keys);
return product;
}
}
Для складніших проєктів використовується BFF (Backend For Frontend) та Edge Functions для прискорення відповіді. Як рекомендовано в документації Express, middleware-ланцюжки дозволяють гнучко розширювати функціональність.
Чому валідація через Zod знижує кількість багів?
Валідація вхідних даних — критична точка. Ми використовуємо Zod: він забезпечує строгу типізацію на рівні TypeScript і автоматично генерує читабельні повідомлення про помилки. За статистикою наших проєктів, перехід на Zod знижує кількість багів, пов'язаних з некоректними даними, на 30–40%.
// src/config/env.ts
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
JWT_REFRESH_SECRET: z.string().min(32),
ALLOWED_ORIGINS: z.string().default('http://localhost:5173'),
});
export const env = envSchema.parse(process.env);
Падіння при старті з явним повідомленням про помилку краще, ніж незрозуміла поведінка в рантаймі при відсутності змінної оточення. До того ж Zod інтегрується зі Swagger для генерації схем.
Налаштування застосунку та middleware
Ключові точки конфігурації — безпека, логування та валідація. Використовуємо helmet, cors з білим списком доменів, pino-http для логів. Валідацію вхідних даних довіряємо zod — це дає строгу типізацію та читабельні помилки. Також налаштовуємо rate limiting (express-rate-limit) та захист від CSRF (csurf).
Як відбувається деплой та моніторинг?
Деплой налаштовуємо через Docker та CI/CD (GitHub Actions). Контейнеризація гарантує відтворюваність оточення. Моніторинг — Sentry для помилок, Grafana для метрик (кількість запитів, час відповіді, використання пам'яті). Типові показники: 99.9% аптайм, час відповіді <100 мс після прогріву кешу, пропускна здатність до 2000 RPS.
Процес роботи
- Аналітика — виявляємо вузькі місця, проєктуємо модулі, обираємо стек (Express, Prisma, Redis).
- Розробка — пишемо модулі, middleware, тести (Vitest). Кожен модуль покриваємо unit-тестами, для API — e2e-тести.
- Документація — генеруємо OpenAPI-специфікацію, яка автоматично оновлюється.
- Деплой — налаштовуємо CI/CD, Docker-контейнеризацію, моніторинг (Sentry, Grafana).
- Підтримка — гарантія 3 місяці, SLA — 4 години в робочий час.
Порівняння інструментів для Express бекенду
| Інструмент | Призначення | Перевага |
|---|---|---|
| Prisma | ORM | Типобезпека, міграції, автокомпліт |
| Redis | Кешування | Час відповіді < 1 мс, підтримка TTL |
| Zod | Валідація | Інтеграція з TypeScript, авто-помилки |
| Pino | Логування | Низьке споживання пам'яті, структуровані логи |
| Vitest | Тестування | Швидкий, сумісний з Vite |
Що входить в роботу
- REST API з модульною архітектурою (8–15 модулів)
- Аутентифікація та авторизація (JWT + refresh tokens)
- Кешування через Redis, інвалідація за ключами
- Graceful shutdown, обробка помилок, логування
- Документація OpenAPI, інструкція з запуску
- Доступ до репозиторію, CI/CD-пайплайн
- Навчання команди (2 години онлайн)
Строки та вартість
Строки: від 4 тижнів для MVP до 8 тижнів для повноцінного продукту. Вартість розраховується індивідуально — залежить від кількості модулів, інтеграцій та необхідної продуктивності. Оцінимо ваш проєкт за 1–2 дні. За рахунок оптимізації кешування та архітектури вдається суттєво економити на інфраструктурі.
Отримайте консультацію — напишіть нам, і ми запропонуємо архітектуру та строки під вашу задачу. Зв'яжіться з нами, щоб обговорити деталі. Наш досвід: понад 5 років на ринку, 50+ успішних проєктів на Node.js, сертифіковані інженери з Express та Prisma.







