昨天我們用 TypeScript 寫了第一個 Node.js API, 今天來讓它「更安全」,尤其是在環境變數這個雷區。
.env
沒有型別 → 容易打錯字或漏值。.env
,必須自己做驗證。npm install dotenv zod
npm install @types/node --save-dev
dotenv
:載入 .env
檔案。zod
:型別驗證函式庫,能回傳 TS 型別。.env
範例.env
PORT=3000
NODE_ENV=development
API_KEY=abc1234567
DATABASE_URL=https://db.example.com
DEBUG=false
CORS_ORIGIN=http://localhost:5173,https://app.example.com
env.ts
驗證檔src/env.ts
import { config } from "dotenv";
import { z } from "zod";
config(); // 請確保最先載入
// 自訂布林轉換
const zBool = z.preprocess((v) => {
if (typeof v === "string") {
const t = v.trim().toLowerCase();
if (["1","true","yes","y","on"].includes(t)) return true;
if (["0","false","no","n","off"].includes(t)) return false;
}
return v;
}, z.boolean());
// CSV → 陣列
const zCsvStringArray = z.preprocess(
(v) => (typeof v === "string" ? v.split(",").map((s) => s.trim()) : v),
z.array(z.string())
);
// 定義 schema
const envSchema = z.object({
PORT: z.coerce.number().int().min(1).max(65535).default(3000),
NODE_ENV: z.enum(["development", "test", "staging", "production"]).default("development"),
API_KEY: z.string().min(10, "API_KEY 長度至少 10"),
DATABASE_URL: z.string().url(),
DEBUG: zBool.default(false),
CORS_ORIGIN: zCsvStringArray.default(["*"]),
RATE_LIMIT_WINDOW_MS: z.coerce.number().int().positive().default(60000),
RATE_LIMIT_MAX: z.coerce.number().int().positive().default(100),
LOG_LEVEL: z.enum(["fatal","error","warn","info","debug","trace"]).default("info"),
});
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error("❌ 環境變數驗證失敗:\n", parsed.error.format());
process.exit(1);
}
export const env = parsed.data;
export const isProd = env.NODE_ENV === "production";
src/index.ts
import express from "express";
import cors from "cors";
import { env } from "./env";
const app = express();
app.use(cors({
origin: env.CORS_ORIGIN.includes("*") ? true : env.CORS_ORIGIN,
}));
app.get("/", (_req, res) => {
res.json({
ok: true,
port: env.PORT,
mode: env.NODE_ENV,
debug: env.DEBUG,
logLevel: env.LOG_LEVEL,
});
});
app.listen(env.PORT, () => {
console.log(`🚀 Server on http://localhost:${env.PORT} (${env.NODE_ENV})`);
});
.env
管理建議不要 commit .env
→ .gitignore
加入:
.env
.env.*.local
建立 .env.example
,只放 key 與示意值:
PORT=3000
NODE_ENV=development
API_KEY=
DATABASE_URL=
DEBUG=false
README 指引:開發者複製 .env.example
→ .env
,再填入真實值。
console.log(process.env)
(避免洩漏秘密)。z.coerce.boolean()
會把 "false"
當 true,需用自訂轉換。NODE_ENV
必須用 z.enum
白名單化,避免拼錯。.env
只適用於本機 / 測試,正式環境請用 Secrets Manager。REDIS_URL
(非必填,缺省時 fallback in-memory)。ENABLE_METRICS
(布林)。TRUST_PROXY
(布林或數字,給 Express)。dotenv-flow
管理多環境檔(dev/staging/prod)。