Два вопроса, которые решают в первый день сервиса: откуда брать настройки и как поднимать и закрывать ресурсы. 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-биндинге. Настроенный конфиг и закрытые на остановке ресурсы (особенно пул базы) — то, без чего сервис нельзя спокойно выкатывать, а спокойный выкат — часть владения, которое несёт продукт-инженер.