Type-Safe env
환경 변수를 조금 더 Type-Safe 할 수 있게 사용할 수 있는 방법을 알아보았습니다.
process.env의 타입 안정성을 높이기 위해 다음 3가지 방법을 시도해볼 수 있습니다.
- 전역 타입 확장하기
- zod 사용하기
- 라이브러리 사용하기
process.env는 Node.js에서 실행 환경의 환경 변수들을 담고 있는 객체인데요! 다들 한 번 쯤은 process.env를 사용할 때 타입 지원이 되지 않는 불편함을 겪어보셨을 것 같습니다. .env 파일에 환경 변수들을 정의해놓았더라도 process.env에는 그 환경 변수들에 대한 자동 완성이 지원되지 않는 등의 불편함 말이죠.
위와 같이, .env 파일에 API_URL을 정의해놓았더라도 정작 process.env에는 TZ(타임존)만 존재하는 것처럼 보입니다.
API_URL을 사용하더라도 타입 에러는 나지 않지만, string | undefined과 같이 undefined이 끼어 있는 걸을 볼 수 있습니다. 개발자가 API_URL을 확실하게 env에 넣어줬더라도, 코드를 작성할 때에는 항상 undefined일 경우를 고려해야 하기에 typescript 설정 중 strict: true인 환경에서는 불편함을 겪게 됩니다.
그래서, 어떻게 하면 정의해놓은 env들에 대해 타입 지원을 받을 수 있을지 알아보겠습니다.
전역 타입 확장하기
가장 쉬우면서도 직관적인 방법입니다!
// globals.d.ts
namespace NodeJS {
interface ProcessEnv {
API_URL: string;
}
}
위와 같이 타입을 그냥 확장해주는 방식인거죠!
TypeScript의 interface는 같은 이름으로 재선언하면 기존에 있던 타입에 더해집니다. Node.js는 ProcessEnv 타입에 TZ(타임존)만 선언해놓은 것을 볼 수 있는데요.
interface로 ProcessEnv를 재선언하면서 이제는 TZ(타임존)과 더불어 API_URL까지 같이 지원되는 것을 확인할 수 있습니다.
zod
ProcessEnv 타입을 확장하는 것보다는 조금 할 일이 많아지지만, 런타임에서 env가 제대로 설정되어 있는지 검증해보며 안정성을 높일 수 있는 방법도 있습니다. 바로 zod를 사용하는 것입니다.
// env.ts
import z from 'zod';
import { config } from 'dotenv';
import path from 'path';
const envPath = path.resolve(__dirname, '../.env');
config({ path: envPath });
const envSchema = z.object({
API_URL: z.string().url(),
});
export const env = envSchema.parse({
API_URL: process.env.API_URL,
});
위에서 만든 env를 가져다 사용한다면 url이면서도 string 타입인 API_URL을 더욱 안정적으로 사용할 수 있습니다.
ZodError: [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"API_URL"
],
"message": "Required"
}
]
만약 API_URL 환경 변수가 설정되어 있지 않거나, url 형태가 아니거나, string이 아니라면 런타임에서 위의 코드가 실행될 때 위처럼 zod에 의해 에러가 발생합니다. zod가 반환해준 메시지를 보며 어떤 사유로 에러가 났고, env가 어떤 형태여야 할지 짐작할 수 있게 되죠.
process.env를 사용했다면 이러한 에러를 내뱉지 않기 때문에 디버깅이 더 힘들었을 겁니다!
라이브러리
t3-env라는 라이브러리를 사용해볼 수도 있습니다. 실제로 제가 회사에서 사용하는 NextJS 환경에 적용한 방법입니다.
원리는 앞서 설명드린 zod를 이용한다는 점은 비슷합니다. /packages/core/src/index.ts에 위치한 createEnv라는 메서드를 보면 zod가 제공하는 z 객체의 object 메서드, safeParse를 사용한다는 것을 확인할 수 있죠.
이 라이브러리가 더 개선한 점은 client 환경과 server 환경을 고려해 더욱 안정성을 높여준다는 점입니다!
흔히 사용하시는 NextJS에서는 NEXT_PUBLIC이라는 prefix를 환경 변수 앞에 붙이면 server 뿐만 아니라 client 측에서도 사용할 수 있어요. 하지만 우리는 가끔 실수를 하곤 합니다. 가령 NEXT_PUBLIC prefix가 붙어 있지 않은, server 측에서만 사용할 DATABASE_URL과 같은 보안이 중요한 환경 변수를 모르고 client 측에서 사용해버리는거죠!
NEXT_PUBLIC prefix가 붙어 있지 않은 DATABASE_URL 환경 변수는 client 측에서는 접근할 수 없기에 노출이 되진 않지만, 해당 환경 변수를 누락하면서 생기는 버그를 디버깅하는 데에는 어려움을 겪게 될 것입니다. 해당 환경 변수에 문제가 있다는 명확한 에러 메시지가 나타나진 않을 것이니까요.
하지만 @t3-oss/env-nextjs를 사용한다면 위와 같이 server 측에서만 사용되는 환경 변수를 client 측에서 접근했다는 에러 메시지를 개발할 때 마주할 수 있습니다. env 때문에 에러가 뜨고 있다는 것을 빠르게 감지할 수 있게 되는 것이죠!
이러한 이유로 저는 회사에서 NextJS를 사용하기에 이 라이브러리를 적용했습니다. client 측만 고려하면 되는 환경이었다면 이 라이브러리를 쓰지 않고 zod만을 사용했을 것 같아요.
사용 방법
이 라이브러리 사용 방법은 공식 문서에 자세하면서도 친절하게 설명되어 있습니다.
이 라이브러리를 사용하면서 또 좋았던 점은, 배포 안정성도 높일 수 있다는 점이었어요. 배포 환경에 새로 추가한 env가 등록되어 있지 않다면 build할 때 에러가 발생합니다. 이 때 또 라이브러리가 남겨준 에러 메시지를 보며 환경 변수를 챙길 수 있게 되죠.
그리고 새로 온 팀원이 README를 깜빡하고 개발 환경을 켜볼 때, 환경 변수를 누락했다라는 것도 빠르게 알 수 있었어요.