← назад к разделу

NESTJS · LIFECYCLE Фаза 7. Конфигурация и lifecycle

NestJS: конфигурация и жизненный цикл приложения

TypeScript · NestJS

ConfigModule, валидация настроек, lifecycle hooks и корректная остановка.

1. ConfigService централизует настройки

1 / 5

ConfigModule читает окружение и файлы конфигурации, ConfigService выдаёт типизированные значения вместо разрозненного process.env.

  • Не читайте env по всему коду.
  • Конфигурация передаётся как dependency.
  • Секреты не логируются.
ConfigModuleenv + config files ConfigServicetyped settingsinjected Providersuse config

2. Настройки проверяются до listen

2 / 5

Обязательные поля, диапазоны и секреты проверяются на старте. Приложение падает с понятной ошибкой до открытия порта.

  • Port преобразуется в number.
  • Отсутствие секрета фатально.
  • Невалидная конфигурация не идёт в production.
Validate config Valid → listenapplication starts Invalid → fail fastclear error, no port

3. onModuleInit инициализирует ресурсы

3 / 5

Lifecycle hook открывает соединения и регистрирует обработчики после создания providers. Асинхронная инициализация завершается до обработки запросов.

  • Инициализация принадлежит владельцу ресурса.
  • Не скрывайте соединения в constructor.
  • Ошибка инициализации видна.
Providers onModuleInitconnect + registerawait ready listen

4. onApplicationShutdown закрывает ресурсы

4 / 5

Hook получает сигнал, прекращает приём работы, ждёт завершения операций и закрывает соединения в обратном порядке.

  • Сначала stop accept.
  • Затем drain и close.
  • Таймаут ограничивает ожидание.
SIGTERM onApplicationShutdownstop + drainclose resources exit

5. Зависимости живут в одном lifecycle

5 / 5

DataSource, очередь, cache и HTTP server открываются и закрываются владельцами. Тесты проверяют инициализацию и отсутствие утечек после остановки.

  • Композиция задаёт порядок.
  • Фоновые задачи отменяются.
  • После close event loop может завершиться.
Application lifecycle DataSourceconnect / close Queuestart / stop HTTP serverlisten / drain

← назад к разделу

Два вопроса встают в первый же день работы с любым сервисом: откуда брать настройки и как правильно открывать и закрывать ресурсы (соединения с базой, очереди, кэш). Разберём, как NestJS решает оба.

Проблема с process.env напрямую

Поначалу кажется удобным читать настройки прямо там, где они нужны:

const secret = process.env.JWT_SECRET; // где-то в глубине сервиса

Через месяц это превращается в проблему: непонятно, какие переменные вообще нужны приложению, ошибки в названиях переменных всплывают не на старте, а в момент конкретного запроса, и тестировать сервис с подменёнными настройками неудобно.

ConfigModule решает это: все настройки читаются в одном месте, проверяются при запуске и отдаются через типизированный ConfigService.

ConfigModule: настройки в одном месте

Устанавливается отдельным пакетом:

npm install @nestjs/config

Подключается в корневом модуле:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
  ],
})
export class AppModule {}

isGlobal: true означает, что ConfigService доступен во всех модулях без дополнительных импортов — подключили один раз и пользуетесь везде.

ConfigModule автоматически читает файл .env в корне проекта и объединяет его с переменными окружения. Переменные окружения имеют приоритет над .env — это удобно: локально работаете с .env, а на сервере задаёте реальные значения через системное окружение.

Как получать значения

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');
  }

  get expiresIn(): string {
    return this.config.get<string>('JWT_EXPIRES_IN', '1h');
  }
}

Два метода с разной семантикой:

  • getOrThrow — если переменной нет, выбрасывает исключение. Используйте для обязательных настроек: лучше упасть на старте с понятной ошибкой, чем гонять undefined по коду.
  • get со вторым аргументом — значение по умолчанию, для опциональных настроек.

Проверка конфигурации на старте

Ещё надёжнее — валидировать весь конфиг при запуске через схему. Для этого обычно используют zod или class-validator с class-transformer:

import { z } from 'zod';

const envSchema = z.object({
  JWT_SECRET: z.string().min(32),
  DATABASE_URL: z.string().url(),
  PORT: z.coerce.number().default(3000),
});

export function validate(config: Record<string, unknown>) {
  const result = envSchema.safeParse(config);
  if (!result.success) {
    throw new Error(`Ошибка конфигурации: ${result.error.message}`);
  }
  return result.data;
}
ConfigModule.forRoot({ isGlobal: true, validate })

Если обязательной переменной нет или она не того формата — приложение падает сразу при старте с понятным сообщением. Это лучше, чем загадочный сбой в рантайме через час после запуска.

Lifecycle-хуки: открываем и закрываем ресурсы правильно

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

NestJS вызывает эти методы автоматически в нужный момент — вам не нужно думать об этом в коде запуска.

Основные хуки по порядку:

ХукКогда срабатывает
onModuleInitмодуль собран, зависимости внедрены
onApplicationBootstrapвсе модули собраны и инициализированы, но сервер ещё не слушает порт
onModuleDestroyначало остановки
beforeApplicationShutdownсигнал получен, соединения ещё открыты — последний шанс дослать ответы
onApplicationShutdownсоединения закрыты, процесс завершается

Принцип прост: что открыли в onModuleInit — закрываем в onApplicationShutdown. Симметрия помогает не забыть очистку.

Graceful shutdown: корректная остановка

Хуки остановки срабатывают только если явно включить обработку сигналов операционной системы:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks();
  await app.listen(3000);
}
bootstrap();

Без enableShutdownHooks() при команде остановки процесс завершится немедленно, не вызвав onApplicationShutdown — соединения останутся незакрытыми, незавершённые запросы потеряются.

Это важно в контейнерном окружении: когда Kubernetes останавливает под, он посылает SIGTERM и даёт время на завершение. Если enableShutdownHooks включён, сервис успевает завершить обработку текущих запросов и закрыть соединения до того, как его принудительно прибьют.

Bootstrap: точка сборки приложения

main.ts — файл, где приложение создаётся и получает глобальные настройки:

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';

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(process.env.PORT ?? 3000);
}
bootstrap();

Здесь в одном месте видно весь «край» сервиса: как обрабатываются входящие данные, как форматируются ошибки, как ведёт себя при остановке. Если понадобится что-то поменять глобально — знаете, куда идти.

Коротко

  • Читайте настройки через ConfigModule + ConfigService, а не напрямую из process.env — всё в одном месте, легче тестировать.
  • isGlobal: true — подключили один раз, доступно везде.
  • Используйте validate для проверки конфига на старте: лучше упасть сразу, чем в рантайме.
  • getOrThrow для обязательных переменных, get с дефолтом — для опциональных.
  • Открывайте ресурсы в onModuleInit, закрывайте в onApplicationShutdown — симметрия не даёт забыть очистку.
  • enableShutdownHooks() обязателен — без него хуки остановки не сработают.

Что пощупать

Настройки стартового каталога практикума remodov/marketplace-system-nodejs читаются из переменных окружения одной функцией fromEnv, а AppModule.forConfig принимает уже готовый объект: тест подставляет свою базу и кэш в памяти, не трогая окружение. Миграции накатываются при старте через migrationsRun, фильтр ошибок и ValidationPipe навешиваются в одном месте - configureApp.

Код: config.ts, app.module.ts, bootstrap.ts, main.ts.

Что почитать дальше