
애플리케이션이 커질수록 환경 변수의 중요성은 점점 더 커진다. 데이터베이스 연결 정보, 외부 API 키, 인증에 사용되는 시크릿 값들은 모두 코드에 직접 포함되어서는 안 되며, 실행 환경에 따라 유연하게 변경될 수 있어야 한다. 특히 개발 환경과 운영 환경이 분리되는 구조에서는 환경 변수 관리 방식이 애플리케이션의 안정성과 직결된다.
Nest.js는 이러한 문제를 프레임워크 차원에서 해결할 수 있도록 설정 관리 시스템을 제공한다. 단순히 .env 파일을 로드하는 수준을 넘어, 설정을 모듈 단위로 구조화하고, 타입 안전하게 주입하며, 애플리케이션 시작 시점에 유효성까지 검증할 수 있도록 설계되어 있다.
@nestjs/config
환경 변수를 효과적으로 관리하고 로드하는 것은 애플리케이션의 유연성과 보안성을 높이는 데 매우 중요하다. Express.js 환경에서는 일반적으로 dotenv 라이브러리를 직접 사용하지만, Nest.js에서는 이를 프레임워크에 맞게 정제한 @nestjs/config 패키지를 공식적으로 제공한다.
@nestjs/config를 사용하면 환경 변수 로딩을 프레임워크의 모듈 시스템 안으로 자연스럽게 통합할 수 있으며, 동적으로 ConfigModule을 생성해 애플리케이션 전반에서 일관되게 설정 값을 관리할 수 있다.
npm i --save @nestjs/configConfigModule.forRoot
ConfigModule의 forRoot 메서드는 ConfigModuleOptions를 인자로 받아 동적 모듈을 생성한다. 이 옵션을 통해 환경 변수 파일의 경로, 전역 모듈 여부, 커스텀 설정 로딩 방식 등을 제어할 수 있다.
특히 isGlobal 옵션을 true로 설정하면 ConfigModule을 글로벌 모듈로 등록할 수 있다. 이렇게 구성하면 @nestjs/config가 제공하는 ConfigService를 별도의 import 없이 모든 컴포넌트에서 주입받아 사용할 수 있다.
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: [
`${__dirname}/config/env/.env.${process.env.NODE_ENV}`,
],
isGlobal: true,
}),
],
})
export class AppModule {}이 방식은 환경별 설정 파일을 명확히 분리하면서도, 애플리케이션 초기화 시점에 필요한 모든 환경 변수를 자동으로 로드할 수 있게 해준다.
커스텀 config와 환경 변수 검증
환경 변수를 사용하다 보면 하나의 .env 파일에 AWS 관련 설정, JWT 설정, 데이터베이스 설정 등이 모두 섞이게 된다. 단순히 문자열 키로 접근하는 방식은 규모가 커질수록 가독성과 유지보수성 측면에서 불리해진다.
@nestjs/config에서 제공하는 registerAs 함수를 사용하면, 환경 변수의 일부를 의미론적인 설정 객체로 분리하여 등록할 수 있다. 이렇게 등록된 설정은 ConfigModuleOptions의 load 옵션을 통해 모듈에 포함되며, 이후 @Inject 데코레이터를 사용해 타입 안전하게 주입받을 수 있다.
export const jwtConfig = registerAs('jwt', () => ({
accessSecret: process.env.JWT_ACCESS_SECRET,
refreshSecret: process.env.JWT_REFRESH_SECRET,
}));
export const awsConfig = registerAs('aws', () => ({
s3: process.env.AWS_S3,
region: process.env.AWS_Region,
}));환경 변수 유효성 검증
환경 변수는 런타임에서만 값이 결정되기 때문에, 애플리케이션 시작 시점에 반드시 유효성을 검증하는 것이 중요하다. 이를 위해 Nest.js 공식 문서에서는 Joi 라이브러리 사용을 권장하고 있다.
validationSchema 옵션을 사용하면 애플리케이션이 부팅되는 시점에 환경 변수의 존재 여부와 타입을 검증할 수 있으며, 조건을 만족하지 못할 경우 즉시 에러를 발생시켜 잘못된 설정 상태로 서버가 실행되는 것을 방지할 수 있다.
export const validationSchema = Joi.object({
AWS_S3: Joi.string().required(),
AWS_Region: Joi.string().required(),
JWT_ACCESS_SECRET: Joi.string().required(),
JWT_REFRESH_SECRET: Joi.string().required(),
});참고로 validate 옵션과 Zod를 사용해 유사한 수준의 검증을 구현할 수도 있다. 다만 공식 문서에서 Joi를 기준으로 설명하고 있기 때문에, 나 역시 Joi를 사용하고 있다.
load 옵션으로 커스텀 config 등록
registerAs로 정의한 설정 객체들은 ConfigModuleOptions의 load 옵션에 배열 형태로 전달된다. 이를 통해 여러 설정을 명확하게 분리한 상태로 모듈에 등록할 수 있다.
@Module({
imports: [
ConfigModule.forRoot({
envFilePath: [
`${__dirname}/config/env/.env.${process.env.NODE_ENV}`,
],
isGlobal: true,
load: [awsConfig, jwtConfig],
validationSchema,
}),
],
})
export class AppModule {}커스텀 config 주입
이렇게 분리된 설정 객체들은 @Inject 데코레이터를 통해 프로바이더에 직접 주입할 수 있다. 이 방식은 문자열 키 기반 접근보다 훨씬 명확하며, 타입 추론을 통해 안전한 코드 작성을 가능하게 한다.
@Injectable()
export class AuthService {
@Inject(jwtConfig.KEY)
private jwt: ConfigType<typeof jwtConfig>;
validateToken() {
const { accessSecret, refreshSecret } = this.jwt;
// ...
}
}NODE_ENV 설정 팁
유닉스 계열 커맨드라인을 사용하는 경우 export 키워드를 통해 NODE_ENV 값을 설정할 수 있다. 현재 설정된 값을 확인하려면 echo 명령어를 사용하면 된다.
export NODE_ENV=production
echo $NODE_ENV
# production이 값은 환경별 .env 파일을 분기하거나, 실행 환경에 따라 애플리케이션 동작을 제어하는 데 자주 활용된다.
더 읽어보기
2025.05.21
14. Redis를 사용한 세션 관리 및 캐싱
새로운 프로젝트를 준비하면서 인증 방식부터 다시 고민하게 되었다. 이전 프로젝트에서는 JWT를 사용해 유저 인증과 상태 관리를 처리했지만, 이번에는 Redis를 활용한 세션 방식으로 전환하기로 결정했다. 이 방식은 유저 상태를 서버에서 직접 관리할 수 있어 보안 측면에서도 유리하고, 필…
2025.01.30
비즈니스 로직은 어디에 있어야 할까
Nest.js에서 컨트롤러와 각각의 프로바이더는 분명한 책임을 갖는다. 하지만 막상 서버를 개발하다 보면, 이 책임들을 깊이 고민하지 않은 채 코드를 작성하게 되고, 그 결과 클래스들이 서로의 영역을 침범하는 상황이 반복된다. 나 역시 컨트롤러에 비즈니스 로직이 섞이거나, 서비스가 지나…
2024.12.07
13. Jest 테스트 구성
이 포스트를 시작하기에 앞서 한 가지 분명히 밝혀두고 싶은 점이 있다. 나는 컨트롤러(혹은 GraphQL 기준으로는 리졸버) 와 레포지토리 클래스에 대한 테스트를 거의 작성하지 않는다.이유는 단순하다. 애플리케이션에서 가장 많은 비즈니스 규칙이 응집되어 있는 곳은 서비스 계층이며, 테스…
2024.11.14
12. GraphQL로 요청 처리
GraphQL은 페이스북에서 개발한 데이터 쿼리 언어이자 런타임으로, 기존 REST API가 가진 구조적 한계를 보완하기 위해 등장했다. 전통적인 REST API에서는 각 엔드포인트마다 반환되는 데이터 구조가 고정되어 있고, 동일한 리소스에 대해 서로 다른 작업을 수행하기 위해 HTTP…
2026.04.11
Trie 자료구조
문자열 데이터를 다룰 때 단순히 “이 단어가 있나?”만으로는 부족한 순간이 있다. 자동 완성처럼 특정 접두사로 시작하는 후보를 모아야 할 때도 있고, 어떤 키에 값을 두고 빠르게 찾고 싶을 때도 있다. 이럴 때 가장 자연스럽게 떠올릴 수 있는 자료구조가 바로 Trie이다.Trie는 무엇…
2026.03.19
Streams API 부록 2. 왜 이미지는 위에서 아래로 나타날까
웹 페이지에서 이미지를 로드할 때 흥미로운 장면을 종종 볼 수 있다. 이미지가 한 번에 완전히 나타나는 것이 아니라, 위에서 아래로 조금씩 채워지면서 나타나는 경우가 있기 때문이다. 특히 네트워크가 느리거나 이미지가 큰 경우 이런 현상이 더 분명하게 보인다. 마치 이미지가 위쪽부터 스캔…
2026.03.19
Streams API 부록 1. HTTP 다운로드 진행률은 어떻게 계산될까
파일을 다운로드할 때 가끔 몇 퍼센트 진행되었는지 혹은 진행 막대(progress bar)가 조금씩 채워지는 모습을 볼 수 있다. 그런데 모든 다운로드가 이런 식으로 진행률을 보여 주는 것은 아니다. 어떤 다운로드는 퍼센트가 표시되지만, 어떤 경우에는 진행 막대 없이 로딩 스피너만 계속…
2026.03.13
Streams API 4. 왜 모든 언어에는 Stream API가 존재할까
Streams API를 공부하다 보면 묘한 기시감을 느끼게 된다. JavaScript에서 ReadableStream, WritableStream, TransformStream을 살펴보고 있는데, 어딘가 낯설지 않다. Java를 써 본 사람이라면 InputStream, OutputStre…
댓글
댓글을 불러오는 중...