Configuration

Configuration

μ‘μš© ν”„λ‘œκ·Έλž¨μ€ λ‹€λ₯Έ ν™˜κ²½μ—μ„œ μ‹€ν–‰λ˜λŠ” 데 μ‚¬μš©λ˜κ³€ ν•©λ‹ˆλ‹€. ν™˜κ²½μ— 따라 λ‹€μ–‘ν•œ ꡬ성 λ³€μˆ˜ μ„ΈνŠΈλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, 둜컬 ν™˜κ²½μ€ νŠΉμ • λ°μ΄ν„°λ² μ΄μŠ€ 자격 증λͺ…에 μ˜μ‘΄ν•  κ°€λŠ₯성이 맀우 λ†’μœΌλ©° 둜컬 DB μΈμŠ€ν„΄μŠ€μ—λ§Œ μœ νš¨ν•©λ‹ˆλ‹€. 이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ ν‚€-κ°’ μŒμ„ λ³΄μœ ν•˜λŠ”.env νŒŒμΌμ„ ν™œμš©ν–ˆμŠ΅λ‹ˆλ‹€. 이 방법은 맀우 νŽΈλ¦¬ν•˜λ―€λ‘œ 각 ν‚€λŠ” νŠΉμ • 값을 λ‚˜νƒ€λƒ…λ‹ˆλ‹€.

κ·ΈλŸ¬λ‚˜ ν”„λ‘œμ„ΈμŠ€ μ „μ—­ 객체λ₯Ό μ‚¬μš©ν•˜λŠ” 경우 ν…ŒμŠ€νŠΈλœ ν΄λž˜μŠ€κ°€ 직접 μ‚¬μš©ν•  수 μžˆμœΌλ―€λ‘œ ν…ŒμŠ€νŠΈλ₯Ό κΉ¨λ—ν•˜κ²Œ μœ μ§€ν•˜κΈ°κ°€ μ–΄λ ΅μŠ΅λ‹ˆλ‹€. 또 λ‹€λ₯Έ 방법은 λ‘œλ“œλœ ꡬ성 λ³€μˆ˜λ‘œ ConfigServiceλ₯Ό λ…ΈμΆœν•˜λŠ” 좔상화 계측 인 ConfigModule을 μž‘μ„±ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

Installation

νŠΉμ • ν”Œλž«νΌμ€ ν™˜κ²½ λ³€μˆ˜λ₯Όprocess.env 전역에 μžλ™μœΌλ‘œ μ—°κ²°ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 둜컬 ν™˜κ²½μ—μ„œλŠ” μˆ˜λ™μœΌλ‘œ 관리해야 ν•©λ‹ˆλ‹€. ν™˜κ²½ νŒŒμΌμ„ ꡬ문 λΆ„μ„ν•˜κΈ° μœ„ν•΄ dotenv νŒ¨ν‚€μ§€λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

$ npm i --save dotenv
$ npm i --save-dev @types/dotenv

Service

λ¨Όμ € ConfigService 클래슀λ₯Ό λ§Œλ“€μ–΄ λ΄…μ‹œλ‹€.

@@filename()
import * as dotenv from 'dotenv';
import * as fs from 'fs';

export class ConfigService {
  private readonly envConfig: { [key: string]: string };

  constructor(filePath: string) {
    this.envConfig = dotenv.parse(fs.readFileSync(filePath))
  }

  get(key: string): string {
    return this.envConfig[key];
  }
}
@@switch
import * as dotenv from 'dotenv';
import * as fs from 'fs';

export class ConfigService {
  constructor(filePath) {
    this.envConfig = dotenv.parse(fs.readFileSync(filePath))
  }

  get(key) {
    return this.envConfig[key];
  }
}

이 ν΄λž˜μŠ€λŠ”.env 파일의 경둜 인 filePathλΌλŠ” 단일 인수λ₯Ό μ·¨ν•©λ‹ˆλ‹€. get()λ©”μ†Œλ“œλŠ” ν™˜κ²½ 파일 내에 μ •μ˜λœ 각 속성을 λ³΄μœ ν•˜λŠ” 개인 envConfig μ˜€λΈŒμ νŠΈμ— μ•‘μ„ΈμŠ€ν•  수 μžˆλ„λ‘ μ œκ³΅λ©λ‹ˆλ‹€.

λ§ˆμ§€λ§‰ λ‹¨κ³„λŠ” ConfigModule을 λ§Œλ“œλŠ” κ²ƒμž…λ‹ˆλ‹€.

@@filename()
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({
  providers: [
    {
      provide: ConfigService,
      useValue: new ConfigService(`${process.env.NODE_ENV}.env`),
    },
  ],
  exports: [ConfigService],
})
export class ConfigModule {}

ConfigModule은 ConfigServiceλ₯Ό λ“±λ‘ν•˜κ³  이λ₯Ό λ‚΄ λ³΄λƒ…λ‹ˆλ‹€. λ˜ν•œ .env 파일의 경둜λ₯Ό μ „λ‹¬ν–ˆμŠ΅λ‹ˆλ‹€. 이 κ²½λ‘œλŠ” μ‹€μ œ μ‹€ν–‰ ν™˜κ²½μ— 따라 λ‹€λ¦…λ‹ˆλ‹€. 이제 μ–΄λ””μ„œλ‚˜ ConfigService λ₯Ό μ£Όμž…ν•˜κ³  μ „λ‹¬λœ ν‚€λ₯Ό 기반으둜 νŠΉμ • 값을 κ°€μ Έμ˜¬ 수 μžˆμŠ΅λ‹ˆλ‹€. μƒ˜ν”Œ .env νŒŒμΌμ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

DATABASE_USER = test
DATABASE_PASSWORD = test

Using the ConfigService

ConfigServiceμ—μ„œ ν™˜κ²½ λ³€μˆ˜μ— μ•‘μ„ΈμŠ€ν•˜λ €λ©΄ λ³€μˆ˜λ₯Ό μ£Όμž…ν•΄μ•Όν•©λ‹ˆλ‹€. λ”°λΌμ„œ λ¨Όμ € λͺ¨λ“ˆμ„ κ°€μ Έμ™€μ•Όν•©λ‹ˆλ‹€.

@@filename(app.module)
@Module({
  imports: [ConfigModule],
  ...
})

그런 λ‹€μŒ μ£Όμž… 토큰을 μ‚¬μš©ν•˜μ—¬ μ£Όμž…ν•  수 μžˆμŠ΅λ‹ˆλ‹€. 기본적으둜 토큰은 클래슀 이름과 κ°™μŠ΅λ‹ˆλ‹€ (예: ConfigService).

@@filename(app.service)
@Injectable()
export class AppService {
  private isAuthEnabled: boolean;
  constructor(config: ConfigService) {
    // Please take note that this check is case sensitive!
    this.isAuthEnabled = config.get('IS_AUTH_ENABLED') === 'true';
  }
}

info 힌트 λͺ¨λ“  λͺ¨λ“ˆμ—μ„œ ConfigModule을 κ°€μ Έ μ˜€λŠ” λŒ€μ‹  ConfigModule을 μ „μ—­ λͺ¨λ“ˆλ‘œ μ„ μ–Έν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

Advanced configuration

방금 κΈ°λ³Έ ConfigServiceλ₯Ό κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ 이 μ ‘κ·Ό λ°©μ‹μ—λŠ” λͺ‡ 가지 단점이 μžˆμŠ΅λ‹ˆλ‹€.

  • ν™˜κ²½ λ³€μˆ˜μ— λŒ€ν•œ 이름 및 μœ ν˜• λˆ„λ½ (IntelliSense μ—†μŒ)

  • 제곡된.env 파일의 확인 λΆ€μ‘±

  • env νŒŒμΌμ€ λΆ€μšΈμ„ λ¬Έμžμ—΄ ('true')둜 μ œκ³΅ν•˜λ―€λ‘œ 맀번 λΆ€μšΈλ‘œ μΊμŠ€νŠΈν•΄μ•Όν•©λ‹ˆλ‹€.

Validation

제곡된 ν™˜κ²½ λ³€μˆ˜μ˜ μœ νš¨μ„± 검증뢀터 μ‹œμž‘ν•˜κ² μŠ΅λ‹ˆλ‹€. ν•„μš”ν•œ ν™˜κ²½ λ³€μˆ˜κ°€ μ œκ³΅λ˜μ§€ μ•Šμ•˜κ±°λ‚˜ 사전 μ •μ˜λœ μš”κ΅¬ 사항을 μΆ©μ‘±ν•˜μ§€ μ•ŠμœΌλ©΄ 였λ₯˜κ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό μœ„ν•΄ npm νŒ¨ν‚€μ§€ Joiλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. Joiλ₯Ό μ‚¬μš©ν•˜λ©΄ 객체 μŠ€ν‚€λ§ˆλ₯Ό μ •μ˜ν•˜κ³  이에 λŒ€ν•΄ JavaScript 객체의 μœ νš¨μ„±μ„ κ²€μ‚¬ν•©λ‹ˆλ‹€.

Joi μ„€μΉ˜ 및 μœ ν˜• (TypeScript μ‚¬μš©μžμ˜ 경우) :

$ npm install --save @hapi/joi
$ npm install --save-dev @types/hapi__joi

νŒ¨ν‚€μ§€κ°€ μ„€μΉ˜λ˜λ©΄ ConfigService둜 이동할 수 μžˆμŠ΅λ‹ˆλ‹€.

@@filename(config.service)
import * as dotenv from 'dotenv';
import * as Joi from '@hapi/joi';
import * as fs from 'fs';

export interface EnvConfig {
}

export class ConfigService {
  private readonly envConfig: EnvConfig;

  constructor(filePath: string) {
    const config = dotenv.parse(fs.readFileSync(filePath));
    this.envConfig = this.validateInput(config);
  }

  /**
   * Ensures all needed variables are set, and returns the validated JavaScript object
   * including the applied default values.
   */
  private validateInput(envConfig: EnvConfig): EnvConfig {
    const envVarsSchema: Joi.ObjectSchema = Joi.object({
      NODE_ENV: Joi.string()
        .valid(['development', 'production', 'test', 'provision'])
        .default('development'),
      PORT: Joi.number().default(3000),
      API_AUTH_ENABLED: Joi.boolean().required(),
    });

    const { error, value: validatedEnvConfig } = Joi.validate(
      envConfig,
      envVarsSchema,
    );
    if (error) {
      throw new Error(`Config validation error: ${error.message}`);
    }
    return validatedEnvConfig;
  }
}

NODE_ENV와 PORT에 기본값을 μ„€μ •ν–ˆμœΌλ―€λ‘œ ν™˜κ²½ νŒŒμΌμ— μ΄λŸ¬ν•œ λ³€μˆ˜λ₯Ό μ œκ³΅ν•˜μ§€ μ•ŠμœΌλ©΄ μœ νš¨μ„± 검사가 μ‹€νŒ¨ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. κ·ΈλŸΌμ—λ„ λΆˆκ΅¬ν•˜κ³  λͺ…μ‹œμ μœΌλ‘œ API_AUTH_ENABLEDλ₯Ό μ œκ³΅ν•΄μ•Ό ν•©λ‹ˆλ‹€. μŠ€ν‚€λ§ˆμ˜ 일뢀가 μ•„λ‹Œ .env νŒŒμΌμ— λ³€μˆ˜κ°€ μžˆλŠ” 경우 μœ νš¨μ„± κ²€μ‚¬μ—μ„œλ„ 였λ₯˜κ°€ λ°œμƒν•©λ‹ˆλ‹€. λ˜ν•œ JoiλŠ” env λ¬Έμžμ—΄μ„ μ˜¬λ°”λ₯Έ μœ ν˜•μœΌλ‘œ λ³€ν™˜ν•˜λ €κ³  μ‹œλ„ν•©λ‹ˆλ‹€.

Class properties

각 ꡬ성 μ†μ„±λ§ˆλ‹€ getter ν•¨μˆ˜λ₯Ό μΆ”κ°€ν•΄μ•Όν•©λ‹ˆλ‹€.

@@filename(config.service)
get isApiAuthEnabled(): boolean {
  return Boolean(this.envConfig.API_AUTH_ENABLED);
}

Usage example

이제 클래슀 속성에 직접 μ•‘μ„ΈμŠ€ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@@filename(app.service)
@Injectable()
export class AppService {
  constructor(config: ConfigService) {
    if (config.isApiAuthEnabled) {
      // Authorization is enabled
    }
  }
}

Last updated

Was this helpful?