일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- nestjs
- 와글와글
- 타입챌린지
- 회고
- TypeScript 타입챌린지
- TypeScript
- type-safe configservice
- 코딩테스트
- 2024년 회고록
- 굿바이 2024년
- type challenge equal type
- configmodule
- 해커톤
- HTTP
- 백엔드
- network
- typeorm
- npm
- equal 타입
- microsoft azure openai
- 스터디
- 월간cs
- 타입 챌린지
- 이펙티브 타입스크립트
- typescript type challenge
- node.js
- Type Challenge
- configservice
- 타입스크립트
- 회고록
- Today
- Total
iamkanguk.dev
[NestJS] Type Safe Config Service 본문
NestJS를 사용하면 환경변수(env)는 보통 @nestjs/config의 ConfigModule을 사용할 것이다.
그렇지만 해당 라이브러리에서 제공하는 ConfigService를 기본으로 사용하면 undefined도 추론되고 해당 env에 그 key가 있는지 없는지도 파악할 수 없다.
그래서 이번 포스팅에서는 어떤 환경 변수들이 있는지 추론이 되고 undefined가 추론되지 않도록 코드를 작성해보려고 한다.
필자는 해당 방법이 가독성도 좋은 것 같고 관리가 생각보다 잘 되는 것 같아서 이 방법을 계속 사용해보려고 한다.
기본적으로 ConfigService를 사용해봤다고 가정해보고 포스팅 작성을 진행해보겠다.
app.config.ts 작성
// src/configs/app.config.ts
import { registerAs } from '@nestjs/config';
import { getEnv } from 'src/common/utils/config.util';
export default registerAs('app', () => ({
//==============================
// SWAGGER CONFIGS
//==============================
SWAGGER: {
USERNAME: getEnv('SWAGGER_DOCS_USERNAME'),
PASSWORD: getEnv('SWAGGER_DOCS_PASSWORD'),
}
}));
위와 같이 코드를 작성하면 섹션별로 나눌 수 있어서 어떤 환경변수들이 있는지 확인하기 굉장히 쉬워진다.
getEnv 함수는 이후에 설명하도록 하겠다. 굉장히 간단하다.
Root Module에 ConfigModule을 등록해보자.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import appConfig from './configs/app.config';
import { AuthModule } from './apis/auth/auth.module';
import { AdminModule } from './apis/admin/admin.module';
import { validationSchema } from '@configs/validation-schema';
import { UserModule } from '@apis/user/user.module';
import { CronModule } from '@libs/cron/cron.module';
import { HospitalModule } from '@apis/hospital/hospital.module';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: `src/configs/envs/.env.${process.env.NODE_ENV}`,
isGlobal: true,
load: [appConfig],
validationSchema: validationSchema,
})
// ...
],
controllers: [],
providers: [],
})
export class AppModule {}
- load 부분에 위에서 registerAs로 선언한 config를 넣어주면 된다. 필자는 그냥 appConfig 하나로 관리해버리는데 더 나누고 싶으면 나눠도 괜찮다.
getEnv 함수 설명
/**
* Get Environment variables by key name
*
* @param key
* @returns
*/
export const getEnv = (key: string): string => {
return process.env[key] ?? '';
};
진짜 별거 없다. 그냥 process.env에서 가져오도록 했고 undefined로 추론되는 것을 막기 위해 Nullish Operator를 통해 없는 경우에는 빈 문자열을 할당하도록 했다.
환경변수 사용하는 법 (Dependency Injection)
@Injectable()
export class UserService {
constructor(
@Inject(appConfig.KEY) private readonly config: AppConfigType,
private readonly prisma: PrismaService,
private readonly awsS3Service: AwsS3Service
) {}
public async test() {
const { ENV1, ENV2 } = this.config.AWS;
}
}
위와 같이 굉장히 쉽게 사용이 가능하다. 그리고 process.env나 configService를 사용할 때는 Type Checking이 안되었을 것이다.
하지만 우리는 app.config.ts 파일에서 섹션별로 선언을 해줬기 때문에 타입이 정의되어서 Type Checking이 가능하다.
그래서 IDE 레벨에서 자동으로 추천해주기도 한다.
다른 모듈에서 Config를 import 해야하는 경우
import appConfig from '@configs/app.config';
import { RedisModule, RedisModuleOptions } from '@liaoliaots/nestjs-redis';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CustomRedisService } from './custom-redis.service';
import { getAppConfig } from 'src/common/utils/config.util';
@Module({
imports: [
RedisModule.forRootAsync({
imports: [ConfigModule.forFeature(appConfig)],
inject: [ConfigService],
// TODO: nestjs-redis 라이브러리 버전 체크 및 Downgrade 필요할수도?
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
useFactory: (configService: ConfigService): RedisModuleOptions => {
const config = getAppConfig(configService);
const { HOST, PORT, PASSWORD } = config.REDIS;
return {
config: {
host: HOST,
port: +PORT,
password: PASSWORD,
},
readyLog: true,
errorLog: true,
};
},
}),
],
providers: [CustomRedisService],
exports: [CustomRedisService],
})
export class CustomRedisModule {}
해당 소스코드는 Redis Module을 작성한 코드이다.
forFeature를 통해 ConfigModule에 설정 파일을 등록하는 것이다. 그러면 configService로 접근을 할 수가 있다는 것이다.
그리고 필자는 getAppConfig라는 유틸 함수를 따로 만들어서 관리를 했다.
getAppConfig 메서드과 AppConfigType
/**
* Get App Config.
* If you use useFactory in module level, you can use this function with applying
* return type.
*
* @param configService
* @returns
*/
export const getAppConfig = (configService: ConfigService): AppConfigType => {
const config = configService.get('app');
if (!config) {
throw new InternalServerErrorException('앱 설정에 문제가 있습니다.');
}
return config;
};
// AppConfigType?
type AppConfigType = ConfigType<typeof appConfig>;
만약에 저렇게 유틸함수로 빼지 않으면 Dynamic Module 등록할 때 저 로직들을 계속 작성해주어야 한다.
중복되는 부분을 제거하기 위해서 유틸 함수로 뺐다고 생각하자.
그리고 AppConfigType은 위와 같이 작성해주면 된다.
이렇게 Config를 Type-Safe하게 관리할 수 있도록 해봤다.
아무래도 TS를 사용하다 보니까 Type Checking이 안되는데에 민감한 것 같다 ㅋㅋ
한번 다른 분들도 적용해보면 좋을 것 같다!
참고자료
- https://suyeonme.tistory.com/109
'Framework > NestJS' 카테고리의 다른 글
[NestJS + OpenAI] Microsoft Azure OpenAI 연동 방법 (1) | 2024.11.26 |
---|---|
[NestJS] Request에 user 정보를 넣으실 때 any 타입으로 하시는 분들 많으시죠? (1) | 2024.01.23 |
[NestJS] TypeORM에서 skip과 take 버그 기록! (0) | 2023.12.20 |
[NestJS] Class-Validator 에러 메세지 커스텀하기! (0) | 2023.12.16 |
[NestJS] 자네 혹시 helmet이 뭔지 알고 쓰나? (0) | 2023.11.29 |