Создание файлов конфигурации в NestJS

Получить значения из файла среды не так просто, как создать .env файл. В NodeJS dotenv, - один из самых известных пакетов, который добавляет значения в process.env узла и возвращает их как string. Но иногда вам нужно, чтобы эти значения были как логические (например, для включения / отключения функции) или массив (например, массив электронных писем поддержки) ... и потому что теперь мы иметь более одного типа, с которым можно поиграть, проверка этих входящих переменных среды становится обязательной.

NestJS - одна из немногих платформ Node, которая упрощает создание переменных конфигурации. Он предоставляет очень объектно-ориентированный, модульный и структурированный способ решения этой проблемы.

Без лишних слов, я собираюсь рассказать вам о том, как вы можете добиться правильной настройки для этого в NestJS.

Необходимые пакеты

  1. @ Nestjs / config - этот пакет был выпущен месяц назад (декабрь 2019 г.) @Kamil Mysliwiec, основателем NestJS и имеет официальную поддержку NestJS.
  2. @ Hapi / joi - используется для создания схемы проверки для входящих переменных среды.

Установка

В этом случае требуются объявления типа только для @ hapi / joi. Те, которые предназначены для @ nestjs / config, входят в состав пакета.

npm i @nestjs/config @hapi/joi
npm i -D @types/hapi__joi

Структура каталогов 📂

На мой взгляд, это очень хороший способ структурировать каталог конфигурации, но вы, очевидно, можете называть файлы в соответствии с вашими требованиями.

src/
└── config/
    ├── app/
    │   ├── config.module.ts
    │   ├── config.service.ts
    │   └── configuration.ts
    ├── database/
    │   └── mongo
    │       ├── config.module.ts
    │       ├── config.service.ts
    │       └── configuration.ts
    └── mail/
        ├── config.module.ts
        ├── config.service.ts
        └── configuration.ts

Переменные среды

В этом посте я возьму config/app в качестве примера, но вы можете использовать аналогичный подход для создания других конфигураций. Давайте возьмем следующее как часть вашего файла переменных среды (.env).

APP_ENV=development
APP_NAME="My App"
APP_URL=http://localhost:9001
APP_PORT=9001

Файлы конфигурации

Оглядываясь на структуру каталогов, для этого нам нужно всего 3 файла.

configuration.ts

Это будет просто функция, инициализирующая ваши переменные именем.

import { registerAs } from '@nestjs/config';
export default registerAs('app', () => ({
  env: process.env.APP_ENV,
  name: process.env.APP_NAME,
  url: process.env.APP_URL,
  port: process.env.APP_PORT,
}));

Обратите внимание, что имя 'app' должно быть уникальным для каждой конфигурации. Итак, для чего-то вроде почты вы можете использовать 'mail' или 'mongo' для своей базы данных mongoDB.

configuration.service.ts

Это простой класс с функциями класса на основе геттера.

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
/**
 * Service dealing with app config based operations.
 *
 * @class
 */
@Injectable()
export class AppConfigService {
  constructor(private configService: ConfigService) {}
  
  get name(): string {
    return this.configService.get<string>('app.name');
  }
  get env(): string {
    return this.configService.get<string>('app.env');
  }
  get url(): string {
    return this.configService.get<string>('app.url');
  }
  get port(): number {
   return Number(this.configService.get<number>('app.port'));
  }
}

Обратите внимание, как get port() возвращает number.

configuration.module.ts

Импортируйте основной ConfigModule NestJS и добавьте свою validationSchema. Вы можете прочитать больше об этом здесь".

import * as Joi from '@hapi/joi';
import { Module } from '@nestjs/common';
import configuration from './configuration';
import { AppConfigService } from './config.service';
import { ConfigModule, ConfigService } from '@nestjs/config';
/**
 * Import and provide app configuration related classes.
 *
 * @module
 */
@Module({
  imports: [
    ConfigModule.forRoot({
      load: [configuration],
      validationSchema: Joi.object({
        APP_NAME: Joi.string().default('MyApp'),
        APP_ENV: Joi.string()
          .valid('development', 'production', 'test', 'provision')
          .default('development'),
        APP_URL: Joi.string().default('http://my-app.test'),
        APP_PORT: Joi.number().default(9000),
      }),
    }),
  ],
  providers: [ConfigService, AppConfigService],
  exports: [ConfigService, AppConfigService],
})
export class AppConfigModule {}

Импорт

Теперь вы можете просто импортировать AppConfigModule в основной модуль вашего приложения (app.module.ts).

import { Module } from '@nestjs/common';
import { AppService } from './app.service';
import { AppController } from './app.controller';
import { AppConfigModule } from './config/app/config.module';
/**
 * Import and provide app related classes.
 *
 * @module
 */
@Module({
  imports: [AppConfigModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Использование его в `main.ts`

Вы можете использовать переменную порта из вашего модуля конфигурации приложения в своей функции начальной загрузки следующим образом:

import { AppModule } from './app.module';
import { NestFactory } from '@nestjs/core';
import { AppConfigService } from './config/app/config.service';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  // Get app config for cors settings and starting the app.
  const appConfig: AppConfigService = app.get('AppConfigService');
  await app.listen(appConfig.port);  // <---- Notice this
}
bootstrap();

Уведомление app.listen(appConfig.port).

Оборотная сторона использования этого подхода

  1. Неизвестные - по умолчанию ConfigModule принимает несколько validationOptions, один из которых - allowUnknown. Если вы установите для него значение false, Nest больше не будет разрешать любые переменные, не входящие в вашу схему проверки. присутствовать в вашем .env файле.
    В таких случаях я бы предложил использовать единичный подход и использовать один модуль для проверки всех переменных, кроме различных сервисов, предоставляющих их. Единственный подход плох только потому, что теперь вам придется сделать его глобальным, и ненужные сервисы будут отправляться на экспорт.
  2. Слишком много папок / файлов - управление таким количеством файлов может быть немного утомительным, но я чувствую, что это является частью использования всего этого фреймворка. Он построен на модульном подходе, и хорошо только следовать тому же принципу во всем коде.

Последние мысли

Чтобы более подробно ознакомиться с этой темой, посетите исходную документацию.

Отзывы и предложения всегда приветствуются. Если этот пост помог, поделитесь своими мыслями в комментариях ниже. Вы всегда можете обратиться ко мне на Medium или к моей команде @Crowdlinker за любой поддержкой по этому поводу.