告別 process.env.UNDEFINED:在專案中實作型別安全的環境變數
5 分鐘
1. process.env 的「陷阱」
在 Node.js 應用程式中,我們透過 process.env 存取環境變數。這是一套簡單有效的機制,但它存在一個固有問題:型別不安全。
process.env 物件的值不是 string,就是 undefined。這會造成幾個常見問題:
- 意外的
undefined:如果你忘了在.env檔案或伺服器上設定某個變數,process.env.MY_VAR在執行階段會是undefined,可能在程式碼深層邏輯中導致TypeError。 - 型別不符:你可能預期連接埠號是
number型別,但process.env.PORT始終是字串,因此必須在使用它的地方手動呼叫parseInt。 - 驗證邏輯分散:我們常在程式碼各處撰寫防禦性程式碼,例如
const port = process.env.PORT || 3000;,使環境變數的管理變得混亂。
2. 核心原則:啟動時失敗(Fail-Fast)
對環境變數這類關鍵設定而言,最佳實務是 「啟動時失敗」(Fail-Fast)。
這代表應用程式應在啟動最初階段,就檢查所有必要環境變數是否均已提供且格式正確。若有任何問題,應用程式應立即擲回錯誤並停止執行,而不是在未來某個不確定的時間點因設定錯誤而崩潰。
這樣能讓我們在部署或開發時立即發現設定問題,而不是等到使用者存取時才發現。
3. 使用 Zod 實作型別安全驗證
Zod 是一個以 TypeScript 為優先的 schema 宣告與驗證函式庫,非常適合用來解決環境變數的型別安全問題。
我們的策略是:
- 為所有環境變數定義一個 Zod schema。
- 在應用程式啟動時,用這個 schema 解析
process.env。 - 如果解析失敗,也就是驗證未通過,Zod 會擲回錯誤,應用程式啟動失敗。
- 如果解析成功,我們會得到一個完整型別化的物件,並在整個應用程式中使用它。
實作範例
首先安裝 Zod:pnpm add zod
接著,建立一個專門處理環境變數的檔案,例如 src/env.ts:
// src/env.ts
import { z } from 'zod';
// 1. 定义 Schema
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
DATABASE_URL: z.string().min(1, 'DATABASE_URL is required.'),
PORT: z.coerce.number().int().positive().default(3000),
// z.coerce 会尝试将字符串转换为数字
});
// 2. 解析和导出
// .parse 会在验证失败时抛出错误,实现 "Fail-Fast"
export const env = envSchema.parse(process.env);現在,你可以在應用程式的任何其他地方從 src/env 匯入 env 物件。
// src/server.ts
import { env } from './env'; // 导入经过验证和类型化的 env 对象
// env.PORT 的类型是 `number`,而不是 `string | undefined`
const port = env.PORT;
// env.NODE_ENV 的类型是 'development' | 'production' | 'test'
if (env.NODE_ENV === 'development') {
console.log('Running in development mode');
}
// 如果 DATABASE_URL 未设置,应用在启动时就已经崩溃了,
// 所以在这里我们可以放心地认为它是存在的,并且类型是 `string`。
connectToDatabase(env.DATABASE_URL);4. 帶來的好處
這種模式能立即帶來好處:
- 完整的型別安全:在程式碼中存取
env.PORT時,TypeScript 知道它是一個number。 - 集中的文件和驗證:
env.ts檔案本身就成了環境變數的權威文件,所有驗證邏輯也都集中於此。 - 可靠的執行階段:杜絕因環境變數拼寫錯誤、缺失或格式不正確而造成的執行階段 Bug。
- Fail-Fast:任何設定問題都會在部署或開發的第一時間被發現。
結論
將環境變數的驗證提前到應用程式啟動時,並使用 Zod 這類工具保證型別安全,是一項投入產出比極高的工程實務。它能顯著提升應用程式的健全性與開發者信心,是現代 TypeScript 專案不可或缺的一環。