Розробка бекенду на 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.







