이번 글에서는 NestJS에서 TypeORM을 연동하고, 환경변수를 안전하게 관리하기 위해
@nestjs/config
과 Joi
를 활용하여 유효성 검증을 추가하는 방법까지 실습해본 내용을 정리해보겠습니다.1. 기본적인 TypeORM 연동 방법
처음에는 가장 단순한 방식으로 TypeORM을 연동해보았습니다.
.env
파일에 정의된 환경변수를 process.env
를 통해 직접 불러오고, 이를 TypeOrmModule.forRoot()
에 주입하는 구조입니다.TypeOrmModule.forRoot({
type: process.env.DB_TYPE as 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT as string),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: [],
synchronize: true, // 개발 환경에서만 true
}),
이 설정만으로도 기본적인 DB 연결은 가능합니다.
synchronize: true
옵션 덕분에 애플리케이션 실행 시 DB 테이블이 자동으로 생성되어, 개발 초기에는 매우 편리하게 사용할 수 있습니다.하지만 이 방식에는 명확한 단점이 있습니다. 바로 환경변수에 대한 유효성 검증이 전혀 이루어지지 않는다는 점입니다. 예를 들어
.env
파일에서 포트 번호를 문자열이 아닌 잘못된 값으로 입력하거나, 필수 값 중 하나가 누락되어 있어도 NestJS는 애플리케이션을 실행하려고 시도합니다. 이로 인해 런타임에서 예기치 못한 오류가 발생할 수 있습니다.2. 환경변수 유효성 검증을 위한 Joi 도입
이러한 문제를 해결하기 위해 NestJS에서는
@nestjs/config
모듈을 사용해 환경변수를 로드하고, Joi
를 통해 유효성 검증을 수행할 수 있도록 지원합니다.우선
ConfigModule
을 전역으로 등록하고, validationSchema
옵션에 Joi.object()
를 지정하여 각 환경변수의 타입, 허용 값, 필수 여부 등을 명시합니다.ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
ENV: Joi.string().valid('dev', 'prod').required(),
DB_TYPE: Joi.string().valid('postgres').required(),
DB_HOST: Joi.string().required(),
DB_PORT: Joi.number().required(),
DB_USERNAME: Joi.string().required(),
DB_PASSWORD: Joi.string().required(),
DB_DATABASE: Joi.string().required(),
}),
}),
💡 왜 Joi를 써야 할까?
- 유효하지 않은 값이 애플리케이션에 반영되는 것을 방지할 수 있습니다.
- 실행 전에 오류를 사전에 인지할 수 있어, 런타임 에러를 줄일 수 있습니다.
- 팀 프로젝트에서
.env
형식이나 값에 대한 일관성을 강제할 수 있습니다.
예를 들어
DB_PORT
가 숫자가 아니거나 DB_TYPE
이 허용된 값(postgres
)이 아닐 경우, NestJS는 서버를 실행시키지 않고 에러를 발생시킵니다. 이는 실무에서 굉장히 중요한 안정성 확보 수단이 됩니다.🔧 3. TypeORM과 ConfigService를 함께 사용하는 방법
환경변수를 안전하게 불러오기 위해서는
ConfigService
를 통해 검증된 값을 가져와야 합니다. 이를 위해 TypeOrmModule.forRootAsync()
를 사용하여, 비동기 방식으로 설정을 주입받도록 구조를 변경합니다.TypeOrmModule.forRootAsync({
useFactory: (configService: ConfigService) => ({
type: configService.get<string>('DB_TYPE') as 'postgres',
host: configService.get<string>('DB_HOST'),
port: configService.get<number>('DB_PORT'),
username: configService.get<string>('DB_USERNAME'),
password: configService.get<string>('DB_PASSWORD'),
database: configService.get<string>('DB_DATABASE'),
entities: [],
synchronize: true, // 개발 시에만 true
}),
inject: [ConfigService],
}),
여기서 중요한 포인트는 다음과 같습니다:
| 항목 | 설명 |
| -------------- | ------------------------------------------------------------------ |
|
useFactory
| 비동기 설정 함수로, DI를 통해 ConfigService
를 주입받아 값을 설정 |
| inject
| useFactory
에 사용할 의존성을 명시 |
| forRootAsync
| 비동기 설정이 필요할 때 사용하는 TypeORM 초기화 메서드 |이 패턴은 추후 프로덕션 배포 환경에서 RDS, AWS Secrets Manager, 환경별 설정 분리 등을 도입할 때도 확장성과 안정성을 보장해줍니다.
예시 .env
파일
ENV=dev
DB_TYPE=postgres
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=yourpassword
DB_DATABASE=mydb
정리하며
이번 실습에서는 NestJS에서 TypeORM을 연동하는 가장 기본적인 방법부터, 환경변수의 유효성 검증을 추가한 안전한 설정 방식까지 함께 적용해보았습니다.
process.env
를 직접 사용하는 방식은 간단하지만, 안정성이 떨어진다@nestjs/config + Joi
조합을 사용하면, 환경변수를 사전에 검증할 수 있어 실무에서도 매우 유용하다TypeOrmModule.forRootAsync
를 사용하면, DI 기반으로 환경변수 설정을 안전하게 주입할 수 있다
NestJS의 강점 중 하나는 이러한 구성요소들을 매우 일관성 있게 구조화할 수 있다는 점입니다. 앞으로 실무 프로젝트에서도 이러한 구조를 바탕으로 환경 설정을 안전하게 유지할 수 있을 것입니다.
이후에는
entities
에 실제 엔티티 클래스를 추가하거나, 환경별 설정 파일(.env.dev
, .env.prod
)을 분리하여 ConfigModule
에서 동적으로 선택하는 방식도 적용해볼 수 있습니다.