이번 포스팅에선 config 를 구성해보도록 하겠습니다.
본래는 프로젝트를 하면 이런것부터 해야하지만 당장 처음엔 딱히 config 로 넣을만한것도 없어서 미루다가,
대충 만들면서 하드코딩으로 들어가있는 slack 관련된 설정값들을 config 로 옮기려 합니다.
- dotenv
config 를 구성할 때 dotenv 라이브러리를 사용하려고 합니다.
dotenv 는 .env 파일을 읽어서 환경 변수로 등록해주는 역할을 하고, 그럼 우리는 환경 변수로부터 필요한 세팅 값들을
사용할 수 있습니다.
팀에서 관리하는 레포들에 dotenv 를 적용해야지 생각만하고 아직 못했었는데 이런때에라도 좀 써보게 되었네요.
> npm install dotenv
먼저 dotenv 라이브러리를 설치합니다.
그리고 당장 .production.env 파일은 쓸 일이 없을 것 같긴 하지만 src 하위에 config/env 디렉토리를 생성하고
.development.env 와 .production.env 파일을 생성합니다.
SLACK_TOKEN=...;
SLACK_CHANNEL=...;
그리고 우선 .development.env 파일에만 위와 같이 환경 변수로 등록할 값을 추가하겠습니다.
지금은 slack 관련된 내용만 넣고있으나 추후엔 DB, redis 주소 및 접근 정보등이 들어갈 것 같습니다.
...
import { config } from 'dotenv';
import * as path from 'path';
config({
path: path.resolve(__dirname, 'config/env/.development.env'),
});
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
transform: true,
}),
);
await app.listen(3000);
}
bootstrap();
좀 더 우아하게 해야겠지만 일단 단순히 main.ts 에서 dotenv 의 config 함수를 호출하는 코드를 추가했습니다.
저렇게 .env 파일 위치를 지정해 config 로 호출하면 dotenv 가 파일을 읽어 등록해줍니다.
import { Injectable } from '@nestjs/common';
import axios from 'axios';
@Injectable()
export class SlackService {
private token: string;
private channel: string;
constructor() {
this.token = '...';
this.channel = '...';
console.info(process.env.SLACK_TOKEN, process.env.SLACK_CHANNEL);
}
...
}
원래 하드코딩되어 있던 부분을 바로 바꾸기 전에 환경 변수에 제대로 세팅이 되었는지를 한번 체크해봤습니다.
그런데 이 상태로는 둘 다 undefined 로 출력이 됩니다.
위 구조는 npm run start:dev 실행시 생성되는 dist 디렉토리인데 여기에 생성했던 config 디렉토리가 보이지 않습니다.
이유는 Nest 가 빌드를 할 때 default 동작이 ts 파일 외의 asset 은 제외하도록 되어있기 때문입니다.
{
...,
"compilerOptions": {
...,
"assets": [
{
"include": "./config/env/*.env",
"outDir": "./dist/src"
}
]
}
}
nest-cli.json 파일에서 compilerOptions 를 위와 같이 수정해줍니다.
내용이 너무 명확해서 딱히 설명할것도 없긴 하지만.. 컴파일할 때 .env 파일도 포함시키도록 하면서 해당 결과물은 dist/src 에 위치하도록
설정한 내용입니다. dotenv 에서 path 의 경로를 보면 outDir 의 위치가 이해되실거라 생각합니다.
이렇게 수정하고 다시 확인하니 이제 process.env 에서 SLACK_TOKEN 과 SLACK_CHANNEL 값이 세팅된게 확인되었습니다.
...
@Injectable()
export class SlackService {
private token: string;
private channel: string;
constructor() {
this.token = process.env.SLACK_TOKEN;
this.channel = process.env.SLACK_CHANNEL;
}
...
}
이제 SlackService 에서 token 과 channel 은 위와 같이 수정해주면 됩니다.
- @nestjs/config
이젠 위에서 main.ts 에 붙여놨던 dotenv 의 설정을 별도의 config 모듈로 만들어보겠습니다.
저대로 두면 보기도 안좋고, 모듈화를 해서 config 가 필요한 각 컴퍼넌트가 ConfigModule 또는 ConfigService 를 주입받아
사용하면 깔끔해질 것 같습니다.
먼저 main.ts 에 추가했었던 dotenv 관련된 내용은 제거하고, dotenv 라이브러리도 제거합니다.
> npm install @nestjs/config
그리고 @nestjs/config 라이브러리를 추가해줍니다.
위에서 dotenv 를 제거한 이유는, @nestjs/config 가 자체적으로 dotenv 를 내장해 활용하기 때문입니다.
라이브러리 페이지에서도 dotenv 를 베이스로 한다고 나와있습니다.
import { Module } from '@nestjs/common';
import { AdsModule } from './ads/ads.module';
import { SlackModule } from './slack/slack.module';
import { ConfigModule } from '@nestjs/config';
import * as path from 'path';
@Module({
imports: [
AdsModule,
SlackModule,
ConfigModule.forRoot({
envFilePath: path.resolve(
__dirname,
process.env.NODE_ENV === 'production'
? 'config/env/.production.env'
: 'config/env/.development.env',
),
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
메인인 app.module.ts 의 imports 부분에 위와 같이 @nestjs/config 에서 제공하는 ConfigModule 을 동적 모듈로 추가합니다.
동적 모듈로 가져오기 위해선 forRoot 메서드를 호출해야하는데, 이 부분은 조금 이따 다시 보겠습니다.
envFilePath 내용은 이전에 main.ts 에서 dotenv 를 사용했을 때와 거의 비슷한 코드입니다.
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { SlackService } from './slack.service';
@Module({
providers: [SlackService, ConfigService],
exports: [SlackService],
})
export class SlackModule {}
추가한 ConfigModule 은 프로바이더인 ConfigService 가 따로 있습니다. 이 ConfigService 를 사용해 환경 변수 값을 가져와야 하니
SlackService 에서 사용할 수 있도록 SlackModule 의 프로바이더에 ConfigService 를 추가해줍니다.
import { Injectable } from '@nestjs/common';
import axios from 'axios';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class SlackService {
private token: string;
private channel: string;
constructor(private readonly configService: ConfigService) {
this.token = this.configService.get('SLACK_TOKEN');
this.channel = this.configService.get('SLACK_CHANNEL');
}
...
}
이제 SlackService 에서 ConfigService 를 주입받아 위와 같이 사용하면, 정상 동작하는 것을 볼 수 있습니다.
- 동적 모듈
위에서 사용했던 동적 모듈에 대해 짧게 보자면 이전까지는 전부 정적 모듈을 사용해 서버를 구성하고 있었습니다.
동적이라는 이름 그대로 동적 모듈은 모듈이 생성될 때 동적으로 변수들이 정해집니다.
여기에 대표적인 모듈이 위에서 작성한 Config 입니다. 이미 위에서 한 것처럼 Config 의 경우엔 서버의 운영 환경인 production,
development, staging 등에 따라 변수들이 달라지게 됩니다. 이런 경우에 우리는 동적 모듈로 구성할 수 있습니다.
...
@Module({
imports: [
...,
ConfigModule.forRoot({
...
}),
],
controllers: [],
providers: [],
})
export class AppModule {}
지금까지 사용했던 정적 모듈과는 다르게 ConfigModule 을 동적 모듈로 구성할 땐 forRoot 메서드를 호출했습니다.
forRoot 는 동적 모듈을 리턴하는 static method 입니다.
지금은 nest 에서 제공하는 ConfigModule 을 사용했지만, 직접 동적 모듈을 반환하는 static method 를 작성할 때도
관례상 forRoot 나 register 네이밍을 사용합니다. 아마 작업하다보면 종종 이런 메서드를 보게 될겁니다.
여기서 비동기가 붙게되면 forRootAsync 나 registerAsync 를 사용합니다.
ConfigModule 외에 이런 메서드를 제공하는건 nest 에서 배치잡을 작성할 때 사용하는 ScheduleModule 이나 메시징을 처리할 때
사용할 수 있는 BullModule 이 forRoot 메서드를 지원했습니다.
ConfigModule 의 forRoot 메서드는 ConfigModuleOptions 타입의 파라미터를 받는데,
이 옵션을 받아 동적으로 ConfigModule 을 생성합니다.
env 관련 코드를 좀 더 정리할 필요가 있는데 추가 내용은 다음 포스팅에서 담도록 하겠습니다.
'Backend > NestJS' 카테고리의 다른 글
NestJS - 대충 서비스 만들어보기 (9) (0) | 2023.05.07 |
---|---|
NestJS - 대충 서비스 만들어보기 (8) (0) | 2023.05.01 |
NestJS - 대충 서비스 만들어보기 (6) (0) | 2023.04.15 |
NestJS - 대충 서비스 만들어보기 (5) (0) | 2023.04.08 |
NestJS - 대충 서비스 만들어보기 (4) (0) | 2023.03.25 |