iT邦幫忙

2025 iThome 鐵人賽

DAY 16
0

昨天我們用 TypeScript 寫了第一個 Node.js API, 今天來讓它「更安全」,尤其是在環境變數這個雷區。

1. 為什麼要驗證環境變數?

  • .env 沒有型別 → 容易打錯字或漏值。
  • 錯的值可能到上線才爆,修復成本極高。
  • TypeScript 沒辦法檢查 .env,必須自己做驗證。

2. 安裝套件

npm install dotenv zod
npm install @types/node --save-dev

  • dotenv:載入 .env 檔案。
  • zod:型別驗證函式庫,能回傳 TS 型別。

3. 建立 .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


4. 建立 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";


5. 在主程式中使用

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})`);
});


6. .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,再填入真實值。


7. 常見踩雷清單

  • 千萬不要 console.log(process.env)(避免洩漏秘密)。
  • z.coerce.boolean() 會把 "false" 當 true,需用自訂轉換。
  • NODE_ENV 必須用 z.enum 白名單化,避免拼錯。
  • .env 只適用於本機 / 測試,正式環境請用 Secrets Manager。

8. 延伸任務(Challenge)

  1. 新增 REDIS_URL(非必填,缺省時 fallback in-memory)。
  2. 新增 ENABLE_METRICS(布林)。
  3. 新增 TRUST_PROXY(布林或數字,給 Express)。
  4. 加入 dotenv-flow 管理多環境檔(dev/staging/prod)。

9. 總結

  • 啟動即驗證,錯誤不會拖到線上才發現。
  • Zod schema = 單一真相來源,自帶型別與預設值。
  • 環境變數不再是黑箱,開發體驗 + 安全性一起提升。

上一篇
Day 15|Node.js + TypeScript 實戰入門
下一篇
Day 17|型別安全的路由參數與查詢參數驗證:express + zod
系列文
我與型別的 30 天約定:TypeScript 入坑實錄19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言