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?