열심히 작성하고 공식 도큐먼트를 확인하니 굳이 dotenv 를 이런 방식으로 사용하지 않고 @nest/config 패키지에서 ConfigModule 을 통해 AppModule 에 임포트해서 설정하는 방식으로 .env 파일을 불러와도 된다고 한다 ㅜㅜ 이와 관련해서 다시 작성해봐야겠다.
오늘 해볼 것은 두구두구 Nest.js 에서 환경 별 환경 변수를 관리하기 위한 방법에 대해서 알아볼까 한다. Nest.js 이긴 하지만, Node.js 를 밑바탕으로 한 모든 환경에서 사용이 가능하다.
이를 위해서 두 라이브러리를 모셔볼까하는데, 첫 번째는 dotenv
이고, 다른 하나는 cross-env
이다.
통상적으로 외부 API 사용을 위한 Key 라던가, 데이터베이스 커넥션을 위해 필요한 다양한 정보(username, password, host 등)는 코드에 포함되면 매우 위험하다. 따라서 이러한 데이터를 외부에 공개되지 않도록 따로 관리하고, 필요한 부분에서 꺼내서 쓰는 것이 안전하다.
dotenv
의 경우, Node.js 환경에서 환경 변수를 파일로 관리할 수 있게끔 해주는 라이브러리라고 한다.
관련한 자료를 찾아보다가 Node.js 환경에서도 환경 변수를 주입할 수 있다고 하는데, 이게 OS 별로 주입하는 데에 차이가 존재한다고 해서 문제가 생길 수 있다고 한다.
이에 대해 간략히 언급을 한 이 블로그를 확인해볼 수 있겠다.
아무튼, dotenv
를 활용하면 환경 변수를 파일에 저장할 수 있고, 이러한 환경 변수를 Node.js 에 대신 등록을 해주는 라이브러리를 dotenv
라고 한다.
dotenv
이 뭔지도 알겠고, 꽤나 유용하게 쓰일 수 있다는 것도 알겠다.
그런데 로컬 환경에서 사용하는 환경 변수, 개발 환경에서 사용하는 환경 변수, 스테이징, 프로덕션 레벨에서 사용하는 환경 변수는 모두 다를 것이다. 운영 환경에서 DB 를 localhost
로 지정해서 사용하지는 않을테니까..
그렇다면 환경 별로 dotenv
의 파일을 달리 사용할 수 있는 방법을 살펴보아야 한다.
가령, .env
파일 하나로만 사용하는 것이 아니라, .env-local
, .env-dev
, .env-stagting
, .env-production
과 같이 환경 별로 따로 파일을 분리하고, 각각의 파일 안에는 환경 별로 필요한 환경 변수들이 내장되어있다.
그리고 이러한 파일을 npm run start:local
, npm run start:dev
와 같이 실행 스크립트를 동작시킬 때 이에 해당하는 dotenv
파일을 동적으로 적용할 수 있어야 할 것이다.
이러한 동작을 위해 필요한 라이브러리가 cross-env
이다.
이에 대해 보다 자세한 설명은 cross-env 공식 도큐먼트를 참고하자.
하나씩 차근차근 해보자. 가장 먼저 dotenv
패키지를 설치해야한다.
$ npm install --save dotenv
프로젝트의 루트 디렉토리에서 .env
파일을 생성한다. 추후 dotenv
패키지를 소스에 임포트 할 때, dotenv
는 가장 먼저 .env
파일을 찾기 때문이다.
그 다음은 해당 파일에 다음과 같이 키: 값
또는 키= 값
의 형태로 환경 변수를 초기화해보자.
TEST_STR: blahblah
나는 키: 값
의 형태로 지정해주었는데, 스프링에서 .yml
파일을 작성할 때 사용했던 방식에 익숙해서 상기와 같이 작성하였다.
이제 사용하면 되는데, 그 전에 config
를 진행해야한다. 나의 경우는 Nest.js 를 사용하고 있기 때문에, 프로젝트 실행 시 가장 먼저 실행되는 엔트리 파일인 main.ts
를 대상으로 작성하였다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// dotenv config
import dotenv = require('dotenv');
dotenv.config();
async function bootstrap() {
const app = await NestFactory.create(AppModule);
console.log('주입된 환경 변수: ' + process.env.TEST_STR);
await app.listen(8080);
}
bootstrap();
이제 어플리케이션을 실행시키면 방금 생성한 .env
파일의 TEST_STR
키에 대한 값이 잘 출력됨을 확인할 수 있다.
이때, TEST_STR
키에 대한 값 주입을 위해 console.log
에 process.env.TEST_STR
을 사용하였는데, process.env
는 Node.js 환경에서 환경 변수를 저장하는 객체이다.
해당 객체 안에 존재하는 환경 변수의 키 값인 TEST_STR
를 불러와서 출력한 것이다.
정상적으로 출력되었으니, 이제 환경 별 dotenv
파일을 cross-env
로 유동적으로 사용할 수 있도록 해보자.
다음 커맨드를 통해 패키지를 설치하자.
$ npm install --save cross-env
설치가 완료되었으면, 환경 별로 사용할 env 파일을 생성해본다.
테스트를 위해 로컬 환경에서 사용할 .env-local
, 클라우드 서버를 통해 여러 개발자들이 공통적으로 개발을 진행할 수 있는 환경이라고 가정하고 개발 환경에서 사용할 .env-dev
파일을 프로젝트의 루트 디렉토리에 생성한다.
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=password
DB_HOST=10.0.1.13
DB_PORT=23389
DB_USERNAME=dev
DB_PASSWORD=devpassword
이제 환경 별로 사용하는 환경 변수가 분리가 되었다.
그 다음 할 일은 어플리케이션 실행 시, 사용할 환경을 주입받는 것이다. 이때 방금 설치한 cross-env
패키지의 커맨드를 사용한다.
Node.js 계열에서 어플리케이션을 실행하기 위해서는 package.json
의 scripts
프로퍼티에 명세된 명령어를 사용한다. 나의 경우, 현재 Nest.js 의 scripts
프로퍼티는 다음과 같이 구성되어 있다.
즉, 개발을 위해서 $ npm run start:dev
커맨드를 사용하여 어플리케이션을 개발 환경으로 구동시키는데, 우리는 local
환경과 dev
환경으로 분리할 것이기 때문에 local
환경을 구동하기 위한 스크립트를 다음과 같이 추가해주었다. (아직은 cross-env 에 대해서 아무 것도 하지 않았다.)
이제 $ npm run start:local
로도 동일하게 어플리케이션을 구동시킬 수 있게 되었다.
이제 본격적으로 cross-env
를 적용해보자.
기존 커맨드에서 cross-env NODE_ENV=local
과 cross-env NODE_ENV=dev
만 추가하였다. 해당 명령어 이후에는 원래 실행시키고 싶었던 명령어를 그대로 써주면 된다.
여기서 NODE_ENV
는 Node.js 의 실행 환경을 담기 위한 환경 변수로서 쓰인다고 한다. 다른 값을 써도 이상이 없는지는 테스트해보지는 않았다.
아무튼, 이렇게 해서 자동으로 dotenv
파일이 환경에 맞게 구동되지는 않는다. 어플리케이션 실행 이후, dotenv
가 어떤 .env
파일을 실행시킬지 정해주어야 한다.
나의 경우, 어플리케이션의 엔트리 포인트는 main.ts
이기 때문에 해당 파일에서 진행하였다.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as path from 'path';
import { Logger } from '@nestjs/common';
import dotenv = require('dotenv');
dotenv.config();
// 환경 별 .env 파일 동작 분기
if (process.env.NODE_ENV === 'local') {
Logger.log('서버가 로컬 환경에서 동작합니다.');
dotenv.config({ path: path.join(__dirname, '../.env-local') });
} else if (process.env.NODE_ENV === 'dev') {
Logger.log('서버가 개발 환경에서 동작합니다.');
dotenv.config({ path: path.join(__dirname, '../.env-dev') });
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
console.log('주입된 환경 변수: ' + process.env.DB_HOST);
console.log('주입된 환경 변수: ' + process.env.DB_PORT);
console.log('주입된 환경 변수: ' + process.env.DB_USERNAME);
console.log('주입된 환경 변수: ' + process.env.DB_PASSWORD);
await app.listen(8080);
}
bootstrap();
여기서 dotenv.config()
의 config()
인자로 옵션 객체를 넘겨줄 수 있다. 이때 옵션 객체 내의 path
프로퍼티를 통해 dotenv
가 동작시킬 .env
를 직접 지정해줄 수 있다.
그리고 어플리케이션이 실행될 때 동작하는 메소드인 bootstrap()
내에 console.log()
를 통해 각 환경 별 환경 변수를 출력해보았다.
$ npm run start:local
로 실행했을 때$ npm run start:dev
로 실행했을 때잘 동작한다. 이를 통해 데이터베이스 Configuration, API 활용 등 다양한 도메인에서 환경 변수를 주입하여 개발 환경에 따라 동적으로 구동시킬 수 있게 되었다.
끗