Два вопроса, которые решают в первый день сервиса: откуда брать настройки и как поднимать и закрывать ресурсы. NestJS отвечает на них ConfigModule и lifecycle-хуками. Если их пропустить, настройки расползаются по process.env в случайных местах, а соединения не закрываются на остановке — и то, и другое всплывает на проде.
Конфигурация: ConfigModule
ConfigModule собирает настройки в одном месте, читает их из переменных окружения и .env, отдаёт через ConfigService.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, validate }),
],
})
export class AppModule {}
isGlobal: true делает ConfigService доступным во всех модулях без повторного импорта. Функция validate проверяет конфиг на старте — если обязательной переменной нет или она неверного типа, приложение падает сразу, а не в середине запроса. Профили окружения (локальный, тестовый, продовый) задаются не кодом, а тем, какие переменные поданы.
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtConfig {
constructor(private readonly config: ConfigService) {}
get secret(): string {
return this.config.getOrThrow<string>('JWT_SECRET');
}
}
getOrThrow честнее, чем get: отсутствие критичной настройки — это падение, а не undefined, гуляющий по коду.
Lifecycle-хуки
Ресурсы, которые надо поднять на старте и закрыть на остановке, привязывают к хукам жизненного цикла. Провайдер реализует нужный интерфейс.
import { Injectable, OnModuleInit, OnApplicationShutdown } from '@nestjs/common';
@Injectable()
export class QueueConsumer implements OnModuleInit, OnApplicationShutdown {
async onModuleInit() {
await this.connect();
}
async onApplicationShutdown(signal?: string) {
await this.disconnect();
}
}
onModuleInit срабатывает, когда модуль собран; onApplicationBootstrap — когда поднято всё приложение; onModuleDestroy и onApplicationShutdown — на остановке. Симметрия «поднял в init — закрыл в shutdown» держит ресурс под контролем и не даёт забыть очистку.
Graceful shutdown
Хуки остановки срабатывают только если включить обработку сигналов — иначе при SIGTERM процесс умрёт, не закрыв соединения.
import { NestFactory } from '@nestjs/core';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();
В Kubernetes это критично: при выкате под получает SIGTERM, и enableShutdownHooks даёт сервису закрыть соединения и доработать запросы, прежде чем уйти.
Bootstrap: сборка приложения
main.ts — точка сборки: тут приложение создаётся и навешиваются глобальные энхансеры.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
app.useGlobalFilters(new AllExceptionsFilter());
app.enableShutdownHooks();
await app.listen(3000);
}
Здесь видно весь край сервиса в одном месте: валидация, формат ошибок, graceful shutdown.
Где это в UCP
Конфигурация и жизненный цикл — это инфраструктура, не бизнес-логика: типизированный конфиг и симметричные хуки делают сервис предсказуемым при запуске и остановке. Это аналог @ConfigurationProperties, профилей и lifecycle-бинов в Spring-биндинге. Настроенный конфиг и закрытые на остановке ресурсы (особенно пул базы) — то, без чего сервис нельзя спокойно выкатывать, а спокойный выкат — часть владения, которое несёт продукт-инженер.