# Configuration

## Configuration

응용 프로그램은 다른 **환경**에서 실행되는 데 사용되곤 합니다. 환경에 따라 다양한 구성 변수 세트를 사용해야 합니다. 예를 들어, 로컬 환경은 특정 데이터베이스 자격 증명에 의존할 가능성이 매우 높으며 로컬 DB 인스턴스에만 유효합니다. 이 문제를 해결하기 위해 키-값 쌍을 보유하는`.env` 파일을 활용했습니다. 이 방법은 매우 편리하므로 각 키는 특정 값을 나타냅니다.

그러나 `프로세스` 전역 객체를 사용하는 경우 테스트된 클래스가 직접 사용할 수 있으므로 테스트를 깨끗하게 유지하기가 어렵습니다. 또 다른 방법은 로드된 구성 변수로 `ConfigService`를 노출하는 추상화 계층 인 `ConfigModule`을 작성하는 것입니다.

## Installation

특정 플랫폼은 환경 변수를`process.env` 전역에 자동으로 연결합니다. 그러나 로컬 환경에서는 수동으로 관리해야 합니다. 환경 파일을 구문 분석하기 위해 [dotenv](https://github.com/motdotla/dotenv) 패키지를 사용합니다.

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

## Service

먼저 `ConfigService` 클래스를 만들어 봅시다.

```typescript
@@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`을 만드는 것입니다.

```typescript
@@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` 파일은 다음과 같습니다.

```typescript
DATABASE_USER = test
DATABASE_PASSWORD = test
```

## Using the ConfigService

`ConfigService`에서 **환경 변수**에 액세스하려면 변수를 주입해야합니다. 따라서 먼저 모듈을 가져와야합니다.

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

그런 다음 주입 토큰을 사용하여 주입할 수 있습니다. 기본적으로 토큰은 클래스 이름과 같습니다 (예: `ConfigService`).

```typescript
@@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](https://github.com/hapijs/joi)를 사용합니다. Joi를 사용하면 객체 스키마를 정의하고 이에 대해 JavaScript 객체의 유효성을 검사합니다.

Joi 설치 및 유형 (**TypeScript** 사용자의 경우) :

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

패키지가 설치되면 `ConfigService`로 이동할 수 있습니다.

```typescript
@@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 함수를 추가해야합니다.

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

## Usage example

이제 클래스 속성에 직접 액세스 할 수 있습니다.

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