Nx NestJs - 환경 변수를 가져오고, 자동 입력하고, 유효성을 검사하는 쉬운 방법

안녕하세요, 오늘은 특히 Nx를 사용하여 NestJ의 환경 변수에 대해 논의할 것입니다.

아시다시피 Nx는 우리를 위해 많은 상용구 코드를 자동 생성합니다. 환경 파일도 마찬가지입니다.
새 프로젝트를 만든 후 대기 중인 2개의 파일environmentenvironment.prod.ts이 있음을 알 수 있습니다.
이러한 파일은 기본적으로 동일하며 프로덕션 플래그가 설정된 경우 빌드 시 .prod 파일이 다른 파일을 대체합니다.
이 모든 마법은 내부에서 볼 수 있습니다project.json.

이제 거기에 모든 것을 간단히 설정할 수 있다면 이 글은 의미가 없을 것입니다. 일반적으로 우리는 응용 프로그램의 비밀을 원합니다… 비밀. 로컬 개발 환경의 경우 구성을 리포지토리에 저장하는 것이 좋을 수 있지만 프로덕션의 경우 절대 그렇지 않습니다.

따라서 많은 것을 단순화하기 위해 혼합 접근 방식을 제안합니다. 로컬의 경우 모든 것을 environment.ts로 설정하고 프로덕션(및 기타)의 경우 비밀이 아닌 항목만 environment.prod.ts에 설정하고 나머지는 환경 변수로 설정합니다.

일하러 가자!

구성



먼저 앱에서 사용할 수 있는 ConfigModule이 필요하므로 계속 실행합니다npm install --save @nestjs/config class-validator class-transformer.

그런 다음 environment 폴더(또는 다른 폴더)에서 다음을 포함할 env-config.ts를 만들 수 있습니다.

/** Environment variables take precedence */
export function getEnvConfig() {
  const envVariables = parseEnvVariables();
  return mergeObject(env, envVariables);
}

function parseEnvVariables() {
  const envVariables: DeepPartial<IEnvironment> = {};
  const env = process.env;
  if (env) {
    if (env.PRODUCTION) envVariables.production = env.PRODUCTION === 'true';
    if (env.PORT) envVariables.port = parseInt(env.PORT, 10);
    if (env.BASE_URL) envVariables.baseUrl = env.BASE_URL;
    if (env.GLOBAL_API_PREFIX) envVariables.globalApiPrefix = env.GLOBAL_API_PREFIX;

    envVariables.database = {};
    if (env.DATABASE_HOST) envVariables.database.host = env.DATABASE_HOST;
    if (env.DATABASE_PORT) envVariables.database.port = parseInt(env.DATABASE_PORT, 10);
    if (env.DATABASE_DATABASE) envVariables.database.database = env.DATABASE_DATABASE;
    if (env.DATABASE_USERNAME) envVariables.database.username = env.DATABASE_USERNAME;
    if (env.DATABASE_PASSWORD) envVariables.database.password = env.DATABASE_PASSWORD;
  }
  return envVariables;
}


function mergeObject(obj1: Record<string, any>, obj2: Record<string, any>) {
  for (const key in obj2) {
    if (obj2.hasOwnProperty(key)) {
      if (typeof obj2[key] === 'object') {
        mergeObject(obj1[key], obj2[key]);
      } else {
        obj1[key] = obj2[key];
      }
    }
  }
  return obj1;
}

type DeepPartial<T> = T extends object
  ? {
      [P in keyof T]?: DeepPartial<T[P]>;
    }
  : T;

getEnvConfig 함수는 시작 시 ConfigModule에 의해 호출되고 environment.ts에서 환경 변수와 환경 개체를 모두 검색합니다. 그런 다음 모든 속성을 병합하고 개체를 반환합니다. 환경 변수는 병합에서 우선 순위를 갖습니다.

일부 인터페이스도 알아차렸을 것입니다. 더 나은 타이핑은 항상 있으면 좋기 때문에 다음을 포함하는 동일한 폴더에 env.interface.ts를 생성해야 합니다.

export interface IEnvironment {
  production: boolean;
  port: number;
  baseUrl: string;
  globalApiPrefix: string;
  database: IDatabaseEnvironment;
}

export interface IDatabaseEnvironment {
  host: string;
  port: number;
  database: string;
  username: string;
  password: string;
}


이 시점에서 우리는 거의 끝났습니다.
우리가 더 해야 할 일은 위의 인터페이스를 environment.tsenvironment.prod.ts에 추가하는 것입니다.
내 파일은 다음과 같습니다.

// environment.ts
import { IEnvironment } from './env.interface';

export const env: Partial<IEnvironment> = {
  production: false,
  globalApiPrefix: 'api',
  port: 3333,
  baseUrl: 'http://localhost',
  database: {
    host: 'localhost',
    port: 5432,
    database: 'postgres',
    username: 'postgres',
    password: 'password',
  },
};


// environment.prod.ts
export const env: Partial<IEnvironment> = {
  production: true,
};


남은 작업은 ConfigModule를 등록하는 것이므로 app.module.ts를 열고 추가합니다.

 ConfigModule.forRoot({
      load: [getEnvConfig],
      isGlobal: true,
      cache: true,
    }),


이것은 당신을 갈 것입니다. 이제 유효성 검사에 대해 이야기하겠습니다.

확인



한 가지 방법은 귀하에게 Joi 있지만 v17 이상의 노드 사용자에게는 권장되지 않습니다.
개인적으로 나는 사용자 지정 유효성 검사를 사용했습니다.
동일한environment 폴더에 다른 파일env-validator을 만듭니다.

import { plainToClass } from 'class-transformer';
import { IsBoolean, IsNumber, IsObject, IsString, validateSync } from 'class-validator';

import { getEnvConfig } from './env-config';
import { IDatabaseEnvironment, IEnvironment } from './env.interface';

class DatabaseEnvironment implements IDatabaseEnvironment {
  @IsString()
  host: string;
  @IsNumber()
  port: number;
  @IsString()
  database: string;
  @IsString()
  username: string;
  @IsString()
  password: string;
}
class Environment implements IEnvironment {
  @IsBoolean()
  production: boolean;

  @IsNumber()
  port: number;

  @IsString()
  baseUrl: string;

  @IsObject()
  database: DatabaseEnvironment;

  @IsString()
  globalApiPrefix: string;
}

export function envValidation() {
const config = getEnvConfig();
  const validatedConfig = plainToClass(Environment, config, { enableImplicitConversion: true });
  const errors = validateSync(validatedConfig, { skipMissingProperties: false });

  if (errors.length > 0) {
    throw new Error(errors.toString());
  }
  return validatedConfig;
}


간단한 설명으로 처음에 생성된 함수를 사용하여 환경 변수를 얻은 다음 변환기 함수를 사용하여 필요한 유효성 검사기를 포함하는 개체를 생성합니다. 그런 다음 각 속성의 유효성을 검사합니다. 유효성 검사에 실패하면 앱이 실행되지 않습니다.

이제 유효성 검사만 트리거하면 됩니다.app.module.ts로 이동하여 변경

ConfigModule.forRoot({
      load: [getEnvConfig],
      isGlobal: true,
      cache: true,
    }),


에게

  ConfigModule.forRoot({
      load: [getEnvConfig],
      isGlobal: true,
      cache: true,
      validate: envValidation,
    })


용도



이 구성에는 많은 용도가 있습니다. 예를 들어 환경 또는 포트에 따라 데이터베이스를 동적으로 변경할 수 있습니다.

사용법 중 하나를 확인해 봅시다. 특히 항구.main.ts를 업데이트하여 ConfigService를 사용하고 env 변수를 가져올 수 있습니다.
그래서 같은 것에서

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


우리는 얻을 수 있습니다

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const config: ConfigService = app.get(ConfigService);
  const globalApiPrefix = config.get<IEnvironment['globalApiPrefix']>('globalPrefix') ?? 'api';

  app.setGlobalPrefix(globalApiPrefix);

  const port = config.get<IEnvironment['port']>('port') ?? 3333;
  await app.listen(port);
}


읽어 주셔서 감사합니다!

좋은 웹페이지 즐겨찾기