[Typescript] dotenv와 cross-env를 이용한 환경변수 설정 및 환경에 따른 환경변수 분리
본 포스팅은 Typescript를 기반으로 작성되었다.
Javascript와 문법이 다르지 않지만 차이점이 존재할 수도 있다는 부분을 염두하고 읽기 바란다.
2021.11.21 - [SW/Typescript] - [Typescript] 소개 및 초기 설치
1. 서두
이번 포스팅에선 dotenv와 cross-env를 이용한 환경변수 관리와 사용에 대해 정리했다.
프로젝트를 개발하다보면 API, DB, Mailing 등 인증이 필요한 작업들을 구현하곤 한다. 우리는 이 과정에서 secret-key, access-key, password, host-domain, port 등의 민감할 수 있는 정보를 소스 코드 안에 집어넣어야 하는 상황을 마주하게 된다. 하지만 그렇다고 진짜 소스 코드에 저런 정보를 그대로 넣기엔 보안상 큰 문제가 발생하고 말것이다. 특히나 소스코드를 버전관리 시스템을 통해 관리 중이라면, 혹여나 실수로 버전관리 시스템의 공개된 레포지토리에 민감한 정보가 저장될 수도 있을 것이다.
그리고 이런 경우도 있을 것이다. 어떤 API의 테스트용 인증키와 실제 운영시 사용되는 인증키가 따로 존재할 경우, 개발환경일때와 프로덕션 환경일때의 인증키 값을 다르게 전달해주어야 한다. 하지만 그렇다고 두 인증키를 직접 소스 코드에 집어넣기엔 보안 위험성이 클 것이다.
일반적으로 이러한 상황에서 소스 코드 내부에 민감한 정보의 흔적을 남기지 않고 해당 정보를 이용할 수 있는 방법은
바로, 환경변수를 이용하는 것이다.
2. Node.js의 기본적인 환경변수 사용법
Node.js에서 환경변수를 사용할 때는 "process.env"라는 JavaScript 내장 전역 객체를 이용한다. 전역 객체이기 때문에 별도로 import 해야하는 모듈이 없고 Node.js 애플리케이션 어디서든 접근할 수 있다.
환경변수를 설정하는 기본적인 방법으로, Node.js 애플리케이션을 실행할 때 "node" 명령어 앞에 "키=값"의 형태로 환경변수를 정의해주면 된다. 이렇게 정의한 환경변수는 해당 Node.js 프로세스가 실행되는 동안에만 존재하게 되고 프로세스가 종료되면 소멸되게 된다.
$ ENV_VARIABLE=test API_KEY=asdf node dist/index.js
아니면 아예 운영체제가 제공하는 명령어를 이용하여 환경변수를 설정하는 방법이 있다. 이런 명령어를 사용하여 정의된 환경변수는 터미널 창을 닫을 때까지 유지되기 때문에 Node.js 프로세스를 종료했다가 다시 실행해도 정의한 환경변수를 이용할 수 있다. 리눅스 또는 맥의 경우 "export 키=값", 윈도우의 경우 "SET 키=값"의 형태로 사용할 수 있다.
[리눅스 또는 맥]
$ export ENV_VARIABLE=test
$ export API_KEY=asdf
[윈도우]
> SET ENV_VARIABLE=test
> SET API_KEY=asdf
하지만 이러한 방법들을 사용하는 환경변수가 많아진다면 매우 번거로운 작업이 될 것이고,
만약 환경변수를 사용하는 다수의 프로젝트를 관리해야한다고 하면 이러한 방법은 프로젝트 별 의존성 문제가 생길 수 있다.
3. dotenv 란?
위와 같은 이유로 인해 다수의 환경변수를 간편하고 의존성 문제 없이 사용할 수 있는 방법이 필요한데,
이런 문제를 해결해주는 패키지가 바로 dotenv 이다.
dotenv는 환경변수 파일을 로드하여 process.env를 통해 해당 파일에 포함된 환경변수를 이용할 수 있게 해준다.
환경변수 파일은 위에서 설명했던 환경변수 설정법처럼 "키=값" 형태의 환경변수를 한 줄에 하나씩 기술하며 .env 확장자로 저장하면 된다 (굳이 .env가 아니어도 상관은 없지만 관례적으로 .env가 해당파일이 환경변수 파일임을 나타낸다.)
ENV_VARIABLE=test
API_KEY=asdf
PORT=8080
HOST=test.com
.env 파일의 예시
주의: .env 파일은 secret-key, access-key, password, host-domain, port 등의 민감할 수 있는 정보를 담고있기 때문에 꼭 .gitignore을 통해 스테이징에서 제외시켜 버전관리 시스템의 공개된 레포지토리에 올라가지 않도록 해야한다.
4. 개발환경 준비
우선 간단하게 아래 명령어로 Typescript를 사용할 수 있는 환경을 만들자.
이미 개발환경이 준비되었다면 이 단계는 건너뛰어도 좋다.
(리눅스 기준의 명령어이다. 윈도우 사용자들도 크게 무리 없이 입력할 수 있는 명령어이긴 하지만, 앞으로의 미래를 위해 윈도우 사용자들은 명령프롬프트, 파워쉘 대신 WSL2를 사용하도록 하자. 그게 정신 건강에 이롭다. WSL2 환경 기반의 개발환경 세팅에 관한 내용은 다른 포스팅에서 자세히 남겨두겠다.)
1
2
3
4
5
6
|
mkdir test
cd test
npm init <= (npm 초기설정값은 아무렇게나 넣어도 상관없다.)
npm install -D typescript <= (필자는 전역 패키지가 의존성을 망쳐놓는걸 선호하지 않기 때문이다.)
npx tsc --init
mkdir src
|
cs |
위 명령어로 생성된 package.json과 tsconfig.json을 아래와 같이 수정해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "dist/index.js", <= dist/index.js 로 수정
"scripts": {
"start": "npx tsc && node .", <= "start"를 추가 하고 "npx tsc && node ." 로 지정
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"typescript": "^4.5.2"
}
}
|
cs |
package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
{
"compilerOptions": {
"target": "ES6",
"lib": ["ES5", "ES6", "ESNext", "DOM"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"sourceMap": true,
"outDir": "./dist",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"exclude": [],
"include": ["src/**/*.ts"]
}
|
cs |
tsconfig.json
5. dotenv 사용법
우선 프로젝트 폴더 최상위 경로에 .env 파일을 생성하고 그 안에 환경변수를 정의 한다.
ENV_VARIABLE=test
API_KEY=asdf
PORT=8080
HOST=test.com
.env
그리고 dotenv를 설치한다.
npm install dotenv
npm install -D @types/dotenv
현재까지의 프로젝트 디렉토리 구조는 아래와 같다.
src 폴더 안에 index.ts를 생성하고 실질적인 코드를 작성한다.
우선 dotenv 모듈과 path 모듈을 import 해준다. (path 모듈은 .env 파일의 경로를 정의하기 위해 사용된다. 물론 그냥 magic-string으로 정의하여도 큰 문제는 없지만 돌아가는 환경에 따라 경로 구분자가 다를 수 있기 때문에 이러한 예외 상황을 미연에 방지하기 위해 path.join( )을 이용할 것이다.)
1
2
|
import dotenv from "dotenv";
import path from "path";
|
cs |
다음으로 dotenv에 환경변수 파일의 경로를 넘겨주면서 우리가 설정한 환경변수를 불러온다.
path.join( )을 이용해 현재 파일(index.ts)의 위치를 기준으로 환경변수 파일의 경로를 표현하여 dotenv.config( )에 path로 넘겨준다.
이때 dotenv.config( )는 환경변수 파일의 parsing 성공 여부를 객체로 반환하는데 반환된 객체 안에 parsed가 포함되어있으면 성공, error가 포함되어있으면 실패이다. 이 성공 여부를 가지고 예외처리를 진행한다.
1
2
3
4
5
6
|
// 일회성 변수가 전역적으로 남는 것을 방지하기 위해 익명함수로 스코프를 제한함
(() => {
const result = dotenv.config({ path: path.join(__dirname, "..", ".env") }); // .env 파일의 경로를 dotenv.config에 넘겨주고 성공여부를 저장함
if (result.parsed == undefined) // .env 파일 parsing 성공 여부 확인
throw new Error("Cannot loaded environment variables file."); // parsing 실패 시 Throwing
})();
|
cs |
환경변수 파일이 정상적으로 로드된 후엔 "process.env.환경변수명"을 통해 환경변수를 사용할 수 있다.
하지만 주의해야할 점은 dotenv를 통한 환경변수 로드는 프로그램 실행 시 가장 먼저 실행이 되도록 해야한다. 만약 환경변수 파일을 로드하기 전에 "process.env.환경변수명"을 통해 환경변수를 사용하면 undefined가 되기 때문에 주의해야 한다.
이제 환경변수 파일을 로드하는 것에 대한 구현이 완료되었는데, 추가적으로 환경옵션에 따라 환경변수를 다르게 로드하는 것을 구현해보도록 하겠다.
6. cross-env
cross-env는 기본적인 환경변수 설정 방법에서 설명한 것처럼 명령줄에서 환경변수를 정의할 수 있게 하는 패키지이다. 하지만 특징이라고 한다면, window나 리눅스나 맥 등 여러 OS마다 기본적으로 명령줄에서 환경변수를 정의하는 방법이 다 제각기인데 cross-env를 사용하게 되면 이를 통일된 방법으로 환경변수를 정의할 수 있다.
이를 이용하여 명령줄을 통해 NODE_ENV라는 환경변수를 정의하여 현재 구동 모드를 development(개발 모드), production(프로덕션 모드), test(테스트 모드)로 구분하여 모드마다 각각 다른 환경변수 파일을 로드하도록 구현할 것이다.
우선 아래 명령어를 통해 cross-env를 설치한다.
npm install -D cross-env
cross-env의 사용법은 아래와 같다.
명령줄 맨 앞에 cross-env를 입력하고 "키=값"의 형태로 환경변수를 정의 한 뒤 node와 .js 파일을 입력하면 된다.
이렇게 하면 process.env.NODE_ENV를 통해 현재 구동 모드를 확인 할 수 있게 된다.
cross-env NODE_ENV=<development, production, test 중 택 1> node dist/index.js
테스트를 위해 .env 파일을 복사하여 "developent.env", "production.env", "test.env"를 만들어두자.
이제 위에서 구현했던 환경변수 로딩 코드에 NODE_ENV를 검사하여 구동 모드에 따라 다른 환경변수 파일을 가져올 수 있도록 수정한다.
1
2
3
4
5
6
7
8
9
10
|
(() => {
const ENV = process.env.NODE_ENV; // NODE_ENV를 변수에 저장
if (!ENV || (ENV !== "development" && ENV !== "test" && ENV !== "production")) // ENV가 유효하지 않은 모드인지 검사
throw new Error("Unknown NODE_ENV"); // 유효하지 않다면 Throwing
const result = dotenv.config({
path: path.join(__dirname, "..", ENV + ".env"), // 모드에 따라 로딩되는 환경변수 파일이
});
if (result.parsed == undefined)
throw new Error("Cannot loaded environment variables file.");
})();
|
cs |
이렇게 하면 환경변수를 파일로 분리하고 구동모드에 따라 다른 환경변수 파일을 로딩할 수 있기 때문에
좀 더 관리 측면에서 유용하게 활용 할 수 있다.