NestJS 애플리케이션을 위한 맞춤형 Config Module 개발
Source: Dev.to
실제 기능보다 환경 변수가 더 많아 고생해 본 적 있나요? DATABASE_URL, SUPABASE_URL, JWT_SECRET, 로컬과 프로덕션을 구분하는 몇 개의 플래그, 그리고 나중에 정리하겠다고 약속한 “임시” 변수들까지.
코드 어디서든 process.env 를 직접 읽어오면 코드베이스가 금방 부서지기 쉽습니다:
- 오타 하나만으로도 연결이 조용히 끊깁니다.
- 변수가 하나 빠지면 런타임에 앱이 크래시합니다.
- 배포 대신 설정 디버깅에 시간을 잡니다.
이 혼란을 더 깔끔하고, 더 전문적으로 처리할 방법이 있다면 어떨까요? 이번 포스트에서는 작고, 타입‑안전하며, 검증된 Config Module을 만들어서, 시리즈의 나머지 부분을 위한 기반으로 삼아보겠습니다.
왜 커스텀 Config 모듈이 필요한가?
Nest는 이미 @nestjs/config를 제공하며, 훌륭합니다. 문제는 대부분의 튜토리얼이 “설치하고 끝”이라는 단계에 머무른다는 점입니다.
프로덕션 수준 API를 위해서는 조금 더 필요합니다:
- 한 곳에서 환경 변수를 로드하고 검증
- 현재는
app과db와 같은 명확한 네임스페이스 - 타입 추론을 통해 설정 접근이 안전하고 탐색 가능하도록
- 빠른 실패 검증 (앱 시작 전에)
이는 우리 개인 재무 API에서 특히 중요합니다. 다음 단계가 안정적인 설정에 의존하기 때문입니다:
- Drizzle은 유효한 Postgres 연결 문자열이 필요합니다
- 로컬 및 프로덕션 환경은 예측 가능하게 동작해야 합니다
모듈 만들기
전역(global)이며 설정에 대한 단일 진실 원천 역할을 하는 모듈을 만들겠습니다.
설정 모듈을 생성합니다(경로는 자유롭게 지정):
nest g mo config
이제 Nest의 ConfigModule을 사용해 모듈을 설정하되, 자체 모듈 안에 감싸서 사용합니다.
// config/config.module.ts
import { Global, Module } from '@nestjs/common';
import { ConfigModule as NestConfigModule } from '@nestjs/config';
@Global()
@Module({
imports: [
NestConfigModule.forRoot({
isGlobal: true,
cache: true,
envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
}),
],
exports: [NestConfigModule],
})
export class ConfigModule {}
이렇게 하면 이미 다음을 제공하게 됩니다:
- 전역 설정(모든 곳에서 별도로 import 할 필요 없음)
- 환경별
.env로드 - 성능을 위한 캐싱
구성 네임스페이스 정의 (app, db)
코드베이스 전역에 변수명을 흩어놓는 대신, 구성 “네임스페이스”를 만들겠습니다. 이렇게 하면 정리가 되고 이후 글들을 따라가기 쉬워집니다.
예시: app config
// config/configurations/app.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs(
'app',
(): AppConfig => ({
port: parseInt(process.env.PORT || '3000', 10),
nodeEnv: (process.env.NODE_ENV || 'development') as AppConfig['nodeEnv'],
}),
);
// config/types/app-config.types.ts
export type AppConfig = {
port: number;
nodeEnv: 'development' | 'production' | 'test' | 'staging';
};
이 시리즈에서 중요한 점은 Drizzle을 연결할 때 db.connectionString과 같은 값을 읽을 수 있다는 것입니다.
Joi를 사용한 환경 변수 검증
타입 안전성도 좋지만 검증은 잘못된 설정이 프로덕션에 도달하는 것을 방지합니다. 애플리케이션이 부팅되기 전에 Joi를 사용해 환경 변수를 검증합니다.
Joi 설치:
pnpm i joi
검증 스키마 생성:
// config/validations/env.validation.ts
import * as Joi from 'joi';
export const envValidationSchema = Joi.object({
PORT: Joi.number().default(3000),
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'staging')
.default('development'),
});
모든 것을 연결:
// config/config.module.ts
import { Global, Module } from '@nestjs/common';
import { ConfigModule as NestConfigModule } from '@nestjs/config';
import appConfig from './configurations/app.config';
import { envValidationSchema } from './validations/env.validation';
@Global()
@Module({
imports: [
NestConfigModule.forRoot({
isGlobal: true,
cache: true,
envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
load: [appConfig],
validationSchema: envValidationSchema,
}),
],
exports: [NestConfigModule],
})
export class ConfigModule {}
빠르게 테스트하기
정의한 변수들을 포함한 .env 파일을 만들고 앱을 부팅하세요. 변수가 누락되었거나 잘못되면 Nest가 즉시 실패하고 정확히 무엇이 문제인지 알려줍니다. 이것이 바로 핵심 포인트입니다.
콘솔에 Nest application successfully started가 표시되면 성공한 것입니다.
마무리
이 시점에서 여러분은 다음과 같은 구성 레이어를 구축했습니다:
- 중앙 집중식 (하나의 모듈)
- 타입‑안전 (구조화된 설정 객체)
- 검증됨 (Joi 스키마)
- 다음 단계 준비 완료 (Drizzle + Supabase 통합)
다음 글에서는 이 모듈을 사용해 Supabase Postgres 연결 문자열로 Drizzle을 초기화하고 스키마 정의를 시작할 것입니다. 하지만 준비하려면 데이터베이스 설정이 필요하므로 이것이 여러분의 과제입니다. 다음 글에서 뵙겠습니다!
💡 다음 글: Drizzle과 우리의 Config Module을 사용해 NestJS에 Supabase Postgres 연결하기.
🔗 코드: https://github.com/RubenOAlvarado/finance-api/tree/v0.2.0