Nest.js에 ConfigModule 설정

kakasoo·2021년 9월 8일
3
post-thumbnail

정적 모듈에서는 환경 변수 사용 불가


이번 글은 짧은 내용을 다룬다. 아마 dotenv를 사용하다가 Nestjs와 ConfigModule을 사용하려던 사람들은 시행착오를 많이 겪을 것이다. 사실 TypeORM을 써서 발생하는 문제는 아닌데, 이런 상황을 주로 만나는 게 아무래도 ORM일 것 같아서 제목에도 넣었다.

Nestjs에는 ConfigModule이 있다. ConfigModule을 사용해서 미리 .env를 삽입해두면 그 하위 모듈에서는 모두 process.env를 통해서 값에 접근할 수 있게 된다. 하지만 아래 같은 경우를 보자.

// nestjs recipe의 typeORM 문서를 보면 아래의 예제 코드가 있다.

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: configService.get('DB_HOST'),
      port: +configService.get<number>('DB_PORT'),
      username: configService.get('DB_USERNAME'),
      database: configService.get('DB_DATABASE'),
      password: configService.get('DB_PASSWORD'),
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: false,  
    }),
    UsersModule,
  ],
})
export class ApiModule {}

이렇게 만들어진 Providers를 DatabaseModule이라는 이름에 넣어주자. 아래처럼.


import { Module } from '@nestjs/common';
import { databaseProviders } from './database.providers';

@Module({
  providers: [...databaseProviders],
  exports: [...databaseProviders],
})
export class DatabaseModule {}

만약 상위 모듈 어디에선가 global하게 ConfigModule을 넣었다면 당연히 되어야 할 것 같은데, 이건 동작하지 않는다. databaseProviders를 import 하지 않고 Module에 직접 작성하여도 바뀌는 건 없다. 이 코드는 여전히 동작하지 않는다. 왜냐하면, process.env에 DB와 관련된 환경 변수들이 없기 때문이다.


그러면 모듈을 동적으로 만들자


모듈은 정적으로 만들어진다. 그리고 dotenv와 달리 Nest에서의 configModule은 정적이다. 그래서 안 된다. 무슨 말이냐면, controller나 service처럼, Module들로 인해 모든 application이 만들어진 다음에야 configModule을 사용할 수 있다는 것이다. module의 생성 시점에는 configModule을 사용할 수 없다.

당연하지만 이에 대한 해결책이 있다.

import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';

@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => {
        return {
          type: 'mysql',
          host: configService.get('DB_HOST'),
          port: +configService.get<number>('DB_PORT'),
          username: configService.get('DB_USERNAME'),
          database: configService.get('DB_DATABASE'),
          password: configService.get('DB_PASSWORD'),
          entities: [__dirname + '/**/*.entity{.ts,.js}'],
          synchronize: false,
        };
      },
    }),
    UsersModule,
  ],
})
export class ApiModule {}

위는 실제로 내가 사용하는 코드이다. 나는 imports에 TypeOrmModule을 넣어주었다. 다시 이 모듈은 imports와 inject를 사용하는데, 말인 즉슨, ConfigModule을 직접 사용할 것임을 뜻한다. 그리고 useFactory라는 키워드를 사용해 설정을 반환하는데, 이 내부에서 configService를 사용한다. useFactory라는 키워드가 바로, 동적으로 모듈을 만드는 방법이다.

이렇게 하고 나면 모듈에서도 ConfigModule에 저장된 값을 사용할 수 있게 된다.

profile
자바스크립트를 좋아하는 "백엔드" 개발자

0개의 댓글