NestJS — серверный фреймворк для Node.js, который строится вокруг модулей, внедрения зависимостей и явных слоёв. Он не навязывает базу данных и шаблонизатор, но задаёт структуру, благодаря которой проект переживает рост команды. Ниже — практическая раскладка каталогов, настройка конфигурации, валидация, работа с БД и запуск в production.
Каркас проекта
CLI создаёт рабочий скелет с TypeScript, тестами и скриптами сборки. Сразу включите строгий режим компилятора: он ловит ошибки типов там, где JavaScript молча отдал бы undefined в ответ API.
npm i -g @nestjs/cli
nest new api --package-manager npm --strict
cd api
npm run start:dev
Раскладка, которая хорошо масштабируется, выглядит так: src/modules — бизнес-модули, src/common — фильтры, интерцепторы и декораторы, src/config — схема переменных окружения, src/database — подключение и миграции. Каждый модуль держит контроллер, сервис, DTO и, при необходимости, репозиторий. Плоский список файлов в корне через полгода превращается в свалку.
Модули и внедрение зависимостей
Модуль — единица композиции. Он объявляет провайдеры и решает, что экспортировать наружу. Сервис не создаётся через new в контроллере: Nest подставляет его по типу конструктора, а контейнер сам разбирает граф зависимостей.
nest g module users
nest g controller users --no-spec
nest g service users --no-spec
Глобальные вещи подключают один раз в AppModule: конфигурацию, логгер, подключение к базе. Если модуль начинает импортировать полпроекта, это сигнал, что границы проведены неверно. Разрывайте циклические зависимости через события или отдельный модуль-посредник, а не через forwardRef по умолчанию.
Конфигурация и валидация
Переменные окружения читаются через @nestjs/config с обязательной схемой. Приложение должно падать на старте, если нет строки подключения или секрета, а не в момент первого запроса. Схему удобно описывать на class-validator: тогда ошибка конфигурации выглядит как обычная ошибка валидации с понятным текстом.
// src/config/env.validation.ts
import { IsIn, IsNumberString, IsString } from 'class-validator';
export class Env {
@IsIn(['development', 'production', 'test']) NODE_ENV: string;
@IsNumberString() PORT: string;
@IsString() DATABASE_URL: string;
@IsString() JWT_SECRET: string;
}
В main.ts включают глобальную валидацию входящих данных: whitelist отбрасывает лишние поля, forbidNonWhitelisted возвращает ошибку, transform приводит типы. Без этого DTO остаётся декорацией, а в сервис приходят произвольные объекты.
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
app.setGlobalPrefix('api');
app.enableShutdownHooks();
Слой данных
Выбор между TypeORM и Prisma не критичен, критична изоляция. Контроллер не должен знать про SQL, а сервис — про формат HTTP-ответа. Репозиторий возвращает доменные объекты, сервис применяет правила, контроллер только переводит их в DTO.
// src/modules/users/users.service.ts
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
findAll(): Promise<User[]> {
return this.prisma.user.findMany({ orderBy: { createdAt: 'desc' } });
}
}
Миграции держите в репозитории и применяйте отдельной командой на деплое, а не автоматически при старте в нескольких репликах. Параллельный запуск миграций — частая причина гонок и полупустых таблиц. Для чтения сложных отчётов используйте отдельные запросы, не пытайтесь собрать всё через жадную загрузку связей.
Ошибки, логи и здоровье
Единый формат ошибок упрощает жизнь клиенту и дежурному. Глобальный фильтр перехватывает исключения, логирует их с идентификатором запроса и отдаёт JSON без внутренних деталей. Идентификатор полезно возвращать в заголовке ответа: по нему инцидент ищется в логах за секунды.
nest g filter common/http-exception
nest g interceptor common/logging
nest g guard auth/jwt
Для проверки живости подключают @nestjs/terminus: отдельный эндпоинт проверяет процесс, память и доступность базы. Readiness-проверка не должна ходить во внешние API — иначе кратковременный сбой партнёра выведет из балансировки весь кластер. Ограничитель запросов @nestjs/throttler ставят на публичные маршруты, а не глобально на всё подряд.
Тесты и границы модулей
Юнит-тесты пишут на сервисы, а не на контроллеры: у сервиса есть зависимости, которые подменяются провайдерами Nest. Для проверки HTTP-слоя поднимают приложение через Test.createTestingModule и вызывают эндпоинт целиком — так ловятся ошибки пайпов, гвардов и формата ответа, которые модульные тесты не видят.
npm run test
npm run test:e2e
npm run lint -- --fix
Держите тесты рядом с кодом и обновляйте их вместе с контрактом API. Если тест приходится переписывать при каждом изменении внутренностей, значит, он проверяет реализацию, а не поведение. Для внешних сервисов используйте подставные адаптеры: сеть в тестах должна быть предсказуемой.
Сборка и запуск
В production запускается скомпилированный JavaScript, а не ts-node. Сборка кладёт файлы в dist, переменные окружения передаются снаружи. Процесс должен корректно завершаться по SIGTERM: Nest закроет HTTP-сервер и соединения с базой, не оборвав запросы на середине.
npm ci
npm run build
npm prune --omit=dev
node dist/main.js
Для нескольких ядер запускают несколько экземпляров за reverse proxy либо используют cluster-модуль Node.js с общим портом. Держите в памяти минимум состояния: сессии — в Redis, файлы — в объектном хранилище, кэш — с явным TTL. Тогда горизонтальное масштабирование сводится к добавлению процесса, а не к переписыванию кода.
Отдельно продумайте миграции и откат: сборка должна быть воспроизводимой, версия образа — неизменной, а схема базы совместимой с предыдущим релизом на время выката. Синие-зелёные или канареечные схемы требуют, чтобы старая и новая версии умели работать с одной базой одновременно. Проверьте это на стенде до первого рабочего деплоя и зафиксируйте порядок отката в регламенте команды. Логи пишите в stdout: их соберёт система. Это экономит время при разборе инцидентов.
Чек-лист перед production
- секреты только в окружении;
- валидация включена глобально;
- health-эндпоинт не зависит от внешних API;
- миграции применяются отдельным шагом.
Итог
NestJS выигрывает не магией, а дисциплиной: модуль, сервис, DTO и явная конфигурация задают предсказуемую структуру. Начните с одного доменного модуля, настройте строгую валидацию окружения и единый формат ошибок, затем добавляйте базу и фоновые задачи. Такой каркас проще тестировать, деплоить и передавать новой команде — а именно это и требуется от серверного Node.js в 2026 году.