КурсыNestJSТема 11

NestJS / 38 минут

Конфигурация и окружения

Проверяйте конфигурацию при запуске и внедряйте типизированные настройки.

В процессе Теория: впереди Практика: впереди Тест: впереди
11 / 14
После этой главы
  • Проверяйте конфигурацию при запуске и внедряйте типизированные настройки.
  • Применить тему в задаче «Конфигурация и окружения» и объяснить граничные случаи.

Около 8 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.

Внешние значения приходят текстом

Окружение отделяет код приложения от настроек конкретного запуска: порта, адреса БД, источника frontend и режима cookie. process.env возвращает строки или undefined. Надпись PORT: number в интерфейсе не изменяет это поведение. Конфигурация является внешним входом и требует проверки так же, как тело HTTP-запроса.

Опасно читать process.env в произвольных сервисах. Тогда проверки и значения по умолчанию расходятся: один компонент принимает пустой адрес, другой заменяет его демонстрационным. Соберите конфигурацию в одном инфраструктурном модуле, проверьте её до обслуживания запросов и передавайте зависимостям уже определённый контракт.

TypeScript
interface AppSettings {
  port: number;
  allowedOrigin: string;
}

Строгий разбор значений

parseInt принимает начальную числовую часть строки, поэтому для порта "80x" одного parseInt недостаточно. Number тоже допускает формы, которые ваш контракт мог не предусматривать. Сначала определите допустимую запись, затем преобразуйте её и проверьте целое число и диапазон. Значения ноль, отрицательное и больше 65535 обычно не подходят для заданного публичного порта.

TypeScript
function parsePort(raw: unknown): number {
  if (typeof raw !== 'string' || !/^[0-9]+$/.test(raw)) {
    throw new Error('PORT должен содержать цифры');
  }
  const value = Number(raw);
  if (!Number.isInteger(value) || value < 1 || value > 65535) {
    throw new Error('PORT вне диапазона');
  }
  return value;
}

Для boolean используйте явные строки true и false; Boolean("false") даёт true. URL проверяйте парсером URL и допустимыми протоколами, а не только наличием точки. Список разрешённых Origin должен содержать точные источники, если приложение использует cookies. Свободная строка конфигурации не является готовой политикой доступа.

ConfigModule и bootstrap

@nestjs/config предоставляет ConfigModule и ConfigService. forRoot может загрузить окружение и вызвать вашу функцию validate. Она принимает сырой объект и возвращает проверенную конфигурацию либо бросает ошибку запуска. Это позволяет обнаружить некорректный адрес или отсутствующий секрет до первого пользовательского запроса.

TypeScript
ConfigModule.forRoot({
  isGlobal: true,
  validate: raw => ({ ...raw, PORT: parsePort(raw.PORT) }),
});

ConfigService.get с generic number не проверяет фактический тип, если validate не преобразовал значение. getOrThrow помогает не пропустить отсутствие обязательного ключа, но не заменяет проверку URL или диапазона. При регистрации клиента БД через async factory передавайте настройки через DI и дождитесь необходимой готовности.

Типизированный провайдер

Для небольшого приложения можно предоставить собственный объект настроек под токеном. useFactory связывает создание значения с контейнером и облегчает подмену в тесте. Лучше возвращать узкий набор нужных полей вместо полного process.env. Сервису каталога не нужны пароль SMTP и секрет другого интеграционного клиента.

TypeScript
const CONFIG = Symbol('CONFIG');
const provider = {
  provide: CONFIG,
  useFactory: (): AppSettings => ({
    port: parsePort(process.env.PORT),
    allowedOrigin: 'http://localhost:3020',
  }),
};

Origin в примере — локальная явная настройка, в production его задают и проверяют отдельно. Подмена провайдера в тесте предпочтительнее изменения глобального process.env между параллельными проверками. Если тест всё же меняет окружение, восстанавливайте его и избегайте конкурентного доступа к общим ключам.

Секреты и клиентская конфигурация

.env с реальными реквизитами не коммитят. .env.example содержит названия переменных и безопасные примеры, по которым разработчик создаёт собственные значения. Случайные credentials генерируют подходящим криптографическим API. Не используйте общий известный пароль как production-default, даже если он облегчает локальный старт.

Публичная runtimeConfig frontend доступна браузеру. Туда допускаются только действительно открытые значения. Адрес внутреннего API, пароль БД и токен runner остаются в серверной конфигурации. Не возвращайте весь объект настроек из /health и не печатайте его целиком в лог при ошибке запуска. Сообщение должно назвать неверный ключ без секретного значения.

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

В упражнении переданный port имитирует внешнюю строку. Не читайте настоящее окружение. Разрешите только цифры и диапазон 1..65535, зарегистрируйте CONFIG через useFactory и получите число из application context. Некорректное значение должно дать null. Контекст обязательно закройте после успешной регистрации.

Проверьте нижнюю и верхнюю границу, ноль и строку с лишним суффиксом. После решения в проекте библиотеки добавьте валидацию адреса БД, режима Secure cookie и точного Origin. Убедитесь, что некорректная конфигурация завершает запуск до listen, а диагностическое сообщение не содержит credentials из URL подключения.

Загружаем редактор…

Музыка: Reverie — Scott Buckley · CC BY 4.0