[NestJS] Config (Environment, Validation)
정의
NestJS Config 는 @nestjs/config 패키지로 환경변수를 관리합니다. .env 파일 로딩, TypeScript type-safe access, 검증 (Joi/Zod), 여러 환경 (dev/stg/prod) 지원. Global module 로 등록해 어디서든 주입.
설치
npm i @nestjs/config
기본 사용
AppModule
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // 다른 module 에 import 없이도 사용
envFilePath: '.env',
cache: true, // 값 캐시 (성능)
expandVariables: true, // ${VAR} 확장
}),
],
})
export class AppModule {}
사용
import { ConfigService } from '@nestjs/config';
@Injectable()
export class MyService {
constructor(private config: ConfigService) {}
someMethod() {
const dbUrl = this.config.get<string>('DATABASE_URL');
const port = this.config.get<number>('PORT', 3000); // 기본값
const isProduction = this.config.get<string>('NODE_ENV') === 'production';
}
}
여러 .env 파일
ConfigModule.forRoot({
envFilePath: [
`.env.${process.env.NODE_ENV}.local`,
`.env.${process.env.NODE_ENV}`,
'.env.local',
'.env',
],
});
우선순위: 첫 번째 파일 값이 우선.
Type Safety
기본 get() 은 loose. 타입 명시 필요:
const port = this.config.get<number>('PORT'); // 타입은 지정하지만 실제는 string
const port = Number(this.config.get('PORT')); // 명시적 변환
Validation (Joi)
배포 시점에 필수 값 검증.
npm i joi
import * as Joi from 'joi';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test', 'staging')
.default('development'),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
JWT_SECRET: Joi.string().required().min(32),
REDIS_HOST: Joi.string().default('localhost'),
REDIS_PORT: Joi.number().default(6379),
LOG_LEVEL: Joi.string()
.valid('error', 'warn', 'info', 'debug')
.default('info'),
}),
validationOptions: {
allowUnknown: true, // 미명시 env 허용 (안 하면 오류)
abortEarly: false, // 모든 오류 표시
},
}),
],
})
export class AppModule {}
앱 시작 시 검증 실패 -> immediate error message.
Zod 대안
npm i zod
import { z } from 'zod';
const configSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test', 'staging']),
PORT: z.string().transform(Number),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
});
type ConfigType = z.infer<typeof configSchema>;
ConfigModule.forRoot({
validate: (config) => configSchema.parse(config),
});
Custom Configuration (namespace)
큰 앱은 config 를 여러 파일로 분리:
// config/database.config.ts
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10) || 5432,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
pool: {
max: parseInt(process.env.DB_POOL_MAX, 10) || 10,
idleTimeout: 30000,
},
}));
// config/redis.config.ts
export default registerAs('redis', () => ({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT, 10) || 6379,
password: process.env.REDIS_PASSWORD,
}));
// config/app.config.ts
export default registerAs('app', () => ({
name: process.env.APP_NAME || 'my-app',
port: parseInt(process.env.PORT, 10) || 3000,
environment: process.env.NODE_ENV,
version: process.env.APP_VERSION,
}));
AppModule:
import databaseConfig from './config/database.config';
import redisConfig from './config/redis.config';
import appConfig from './config/app.config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [databaseConfig, redisConfig, appConfig],
validationSchema: Joi.object({...}),
}),
],
})
사용
// namespaced access
const dbConfig = this.config.get('database');
const dbHost = this.config.get('database.host');
const poolMax = this.config.get<number>('database.pool.max');
Type-safe namespace
type DatabaseConfig = ConfigType<typeof databaseConfig>;
@Injectable()
export class UsersService {
constructor(
@Inject(databaseConfig.KEY)
private db: DatabaseConfig,
) {}
async connect() {
console.log(this.db.host); // typed
}
}
Async Loading (Vault, AWS Parameter Store)
.env 대신 외부 secret store 에서:
ConfigModule.forRoot({
isGlobal: true,
load: [async () => {
const secrets = await getFromVault();
return {
DATABASE_URL: secrets.dbUrl,
JWT_SECRET: secrets.jwtSecret,
};
}],
});
주의: async config 는 앱 부트스트랩 지연. Fallback / caching 필요.
AWS SSM Parameter Store 예
import { SSMClient, GetParametersByPathCommand } from '@aws-sdk/client-ssm';
async function loadFromSSM(): Promise<Record<string, string>> {
const client = new SSMClient({region: 'us-east-1'});
const command = new GetParametersByPathCommand({
Path: `/myapp/${process.env.NODE_ENV}/`,
WithDecryption: true,
Recursive: true,
});
const response = await client.send(command);
const result: Record<string, string> = {};
for (const param of response.Parameters || []) {
const key = param.Name.split('/').pop().toUpperCase();
result[key] = param.Value;
}
return result;
}
Variable Expansion
# .env
BASE_URL=https://api.example.com
USERS_ENDPOINT=${BASE_URL}/users
POSTS_ENDPOINT=${BASE_URL}/posts
ConfigModule.forRoot({
expandVariables: true,
});
계층: env -> config file -> defaults
// config/default.config.ts
export default {
cache: {
ttl: 60,
max: 1000,
},
};
// config/production.config.ts
export default {
cache: {
ttl: 3600,
},
};
// 병합 로직
이 패턴은 표준화 안 됨. nest-config 같은 별도 패키지 검토.
Feature Config
한 모듈만 사용:
// UsersModule
@Module({
imports: [
ConfigModule.forFeature(usersConfig),
],
})
isGlobal: false (기본) 로 다른 module 에서 격리.
프로덕션 관용
12-Factor App
- 환경변수로 config
- 코드에 secret 없음
- 환경별 파일 (
.env.dev,.env.prod) 지양, 환경변수 직접
secret 관리
- Kubernetes: Secret + ExternalSecrets Operator
- AWS: Secrets Manager, SSM Parameter Store, KMS
- Vault: HashiCorp Vault
- Doppler / 1Password / Infisical: SaaS
Docker/K8s 에서는 .env 파일 대신 환경변수 마운트.
함정
WARNING
.env 파일 git 커밋 금지. .gitignore 필수. 실수로 커밋 시 secret rotation.
CAUTION
process.env.X 는 항상 string. 숫자, boolean 은 명시적 변환.
WARNING
Validation 없으면 조용히 실패. 필수 env 누락되면 undefined 로 부팅 후 나중 오류.
IMPORTANT
isGlobal: true 남용. Global 은 어디서든 주입 가능. Feature config 는 격리가 나음.
CAUTION
Test 에서 config 격리. process.env 조작은 다른 test 로 leak. beforeEach 에서 restore.
관련 위키
- NestJS - 상위 개요
- Modules - Dynamic module
- Database - DB config
- Deployment - env 주입
- Testing
- Microservices - broker config
- Pydantic Settings - 대비
- Flask Config - 대비
이 글의 용어 (8개)
- [FastAPI] Pydantic Integrationfastapi
- 정의 FastAPI + Pydantic 은 요청/응답의 타입 검증, 직렬화, JSON Schema 생성 을 위한 통합입니다. Pydantic v2 는 Rust 코어 ( ) 로 v…
- [Flask] Application Factory Patternflask
- 정의 Application Factory 는 Flask 앱 인스턴스를 함수 안에서 생성 하는 패턴입니다. 모듈 스코프에 를 두는 대신 함수를 정의해 반환합니다. 이 패턴은 사실상…
- [Framework] NestJSframeworks
- 정의 NestJS 는 Kamil Myśliwiec 이 2017년 발표한 Node.js 서버측 프레임워크 입니다. TypeScript first, Angular 에서 영감받은 모듈…
- [NestJS] Database (TypeORM, Prisma, Mongoose, Drizzle)frameworks
- 정의 NestJS Database 통합은 여러 ORM/ODM 을 module 로 감싸 DI + 트랜잭션 + 마이그레이션을 관용화합니다. 주요 선택지: TypeORM (성숙), P…
- [NestJS] Deployment (Docker, Kubernetes, PM2)frameworks
- 정의 NestJS Deployment 는 프로덕션 환경에서 앱을 배포/운영하기 위한 절차입니다. Docker 이미지 빌드, Kubernetes 매니페스트, PM2 프로세스 관리,…
- [NestJS] Microservices (Kafka, NATS, RabbitMQ, gRPC, TCP)frameworks
- 정의 NestJS Microservices 는 HTTP 대신 message broker (Kafka, RabbitMQ, NATS, Redis) 나 gRPC / TCP 로 통신하는…
- [NestJS] Modulesframeworks
- 정의 NestJS Module 은 데코레이터가 붙은 클래스로, 관련된 controller, provider, import/export 를 하나로 묶는 조직 단위입니다. Angul…
- [NestJS] Testing (Jest, Supertest, e2e)frameworks
- 정의 NestJS Testing 은 Jest (기본) + Supertest 조합으로 unit test 와 e2e test 를 지원합니다. 의 로 module 을 격리된 형태로 만…
💬 댓글