本文 AI 產出,尚未審核

ExpressJS (TypeScript) – 部署與環境分離

Production vs Development 設定


簡介

在開發 ExpressJS 應用時,我們常會把程式碼直接跑在本機環境,然後把同樣的程式碼直接部署到正式機器。這種做法雖然簡單,但 開發環境正式環境 的需求差異很大:開發時需要大量除錯資訊、熱重載與寬鬆的錯誤容忍;正式環境則要求效能、資源安全與穩定性。若不把兩者的設定分離,會導致:

  1. 效能浪費:開發時才需要的日誌、錯誤堆疊在正式環境仍被輸出,浪費 I/O 與 CPU。
  2. 安全隱憂:開發環境常把敏感資訊(如測試 API 金鑰)寫死在程式碼,正式環境若未切換就會外洩。
  3. 維護困難:同一份程式碼同時兼顧兩種需求,會讓設定變得雜亂不易追蹤。

因此,將設定依據 NODE_ENV(或自訂環境變數)切換,是每個想要把 ExpressJS 運行在生產環境的開發者必備的基礎功。本文將以 TypeScript 為例,說明如何在不同環境間切換設定,並提供實作範例、常見陷阱與最佳實踐,讓你在部署時更得心應手。


核心概念

1. 為什麼要使用 NODE_ENV

NODE_ENV 是 Node.js 社群慣用的環境變數,約定俗成的值有:

意義
development 本機開發、開啟除錯資訊
production 正式部署、關閉除錯、開啟效能優化
test 單元/整合測試專用

在程式碼中只要判斷 process.env.NODE_ENV,即可決定要載入哪套設定檔或啟用哪些中介軟體(middleware)。

2. 使用 .env 檔案管理環境變數

dotenv 套件可以把 .env 檔中的 KEY=VALUE 讀入 process.env,讓設定與程式碼分離。建議在根目錄放置:

.env.development
.env.production
.env.test

並在啟動指令中指定要載入哪個檔案,例如:

// package.json scripts
{
  "scripts": {
    "dev": "cross-env NODE_ENV=development ts-node-dev src/index.ts",
    "start": "cross-env NODE_ENV=production node dist/index.js",
    "test": "cross-env NODE_ENV=test jest"
  }
}

⚠️ 注意:千萬不要把 .env.* 檔案直接提交到 Git,應在 .gitignore 中排除,並使用 GitHub SecretsCI/CD 工具注入正式環境變數。

3. 設定檔的結構化

將設定抽成 config 目錄,依環境分別匯出設定物件,最終再由一個 index.ts 統一載入:

src/
 └─ config/
     ├─ default.ts
     ├─ development.ts
     ├─ production.ts
     └─ index.ts

default.ts

// src/config/default.ts
export default {
  port: 3000,
  corsOrigin: "*",
  logLevel: "info",
};

development.ts

// src/config/development.ts
import defaultConfig from "./default";

export default {
  ...defaultConfig,
  corsOrigin: "http://localhost:3000",
  logLevel: "debug",
  enableMorgan: true,   // 開啟 HTTP request logger
};

production.ts

// src/config/production.ts
import defaultConfig from "./default";

export default {
  ...defaultConfig,
  corsOrigin: "https://myapp.com",
  logLevel: "warn",
  enableMorgan: false, // 關閉開發用 logger
  enableCompression: true,
  enableHelmet: true,
};

index.ts

// src/config/index.ts
import development from "./development";
import production from "./production";
import defaultConfig from "./default";

type Config = typeof defaultConfig;

let config: Config;

switch (process.env.NODE_ENV) {
  case "production":
    config = production;
    break;
  case "development":
    config = development;
    break;
  default:
    config = defaultConfig;
    break;
}

export default config;

重點:所有設定都集中在 config,程式碼只需要 import config from "./config",即可根據環境自動取得正確值。

4. 程式碼範例:依環境載入 Middleware

以下示範在 src/app.ts 中根據設定動態注入中介軟體。

// src/app.ts
import express, { Request, Response, NextFunction } from "express";
import cors from "cors";
import helmet from "helmet";
import compression from "compression";
import morgan from "morgan";
import config from "./config";

const app = express();

// 1️⃣ 基本設定
app.use(express.json());
app.use(cors({ origin: config.corsOrigin }));

// 2️⃣ 開發環境專屬:Morgan 日誌
if (config.enableMorgan) {
  app.use(morgan("dev"));
}

// 3️⃣ 正式環境安全與效能:Helmet + Compression
if (config.enableHelmet) {
  app.use(helmet());
}
if (config.enableCompression) {
  app.use(compression());
}

// 4️⃣ 範例路由
app.get("/", (req: Request, res: Response) => {
  res.send("Hello Express with TypeScript!");
});

// 5️⃣ 錯誤處理(依環境調整回傳訊息)
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
  const status = (err as any).status || 500;
  const message =
    process.env.NODE_ENV === "production"
      ? "Internal Server Error"
      : err.message;
  res.status(status).json({ error: message });
});

export default app;

5. 程式碼範例:使用 dotenv 讀入環境變數

// src/server.ts
import "dotenv/config"; // 會自動載入根目錄的 .env
import http from "http";
import app from "./app";
import config from "./config";

const server = http.createServer(app);

server.listen(config.port, () => {
  console.log(
    `[${process.env.NODE_ENV?.toUpperCase() || "UNKNOWN"}] Server listening on port ${config.port}`
  );
});

說明dotenv/config 會根據 NODE_ENV 自動尋找 .env.{NODE_ENV},若找不到則載入根目錄的 .env

6. 程式碼範例:在 Docker 中切換環境

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm ci
COPY src ./src
RUN npm run build

FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --only=production

# 設定環境變數
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]

docker-compose.yml 中可為開發環境覆寫:

version: "3.9"
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
    volumes:
      - .:/app
      - /app/node_modules
    command: npm run dev

常見陷阱與最佳實踐

陷阱 可能的後果 解決方案 / 最佳實踐
忘記在正式環境設定 NODE_ENV=production Express 仍會以開發模式啟動(例如 trust proxyfalseview cachefalse),導致效能下降。 在 Dockerfile、PM2、systemd 或 CI/CD pipeline 中明確設定 NODE_ENV=production
在程式碼中硬編碼敏感資訊(如 DB 密碼) 正式環境的程式碼洩漏會造成資安風險。 所有機密資訊皆放在環境變數或密鑰管理服務(AWS Secrets Manager、Azure Key Vault)。
.env.production 中留下測試用設定 測試資料不小心寫入正式資料庫。 使用 不同的資料庫帳號不同的資料庫,在 CI/CD 中嚴格檢查環境檔內容。
開發用 logger 未關閉 大量日誌寫入磁碟或 CloudWatch,增加成本。 config.enableMorgan 控制 morgan,或在 production 設定 logLevel: "warn"
未啟用 helmetcompression 缺少安全標頭、回應體積過大。 production 設定 enableHelmetenableCompressiontrue
在測試環境仍使用真實外部服務 測試不穩定、成本上升。 使用 nockmsw 或 mock 服務,並在 test 設定檔中關閉外部連線。

最佳實踐總結

  1. 環境變數永遠外部化.env.*、CI/CD、K8s ConfigMap/Secret。
  2. 設定檔模組化default + environment-specific,避免重複。
  3. 中介軟體依環境載入:只在需要的環境啟用。
  4. 錯誤訊息隱藏:正式環境只回傳通用訊息,避免資訊外泄。
  5. 自動化測試:在 test 環境驗證所有設定切換正確。

實際應用場景

1. 多租戶 SaaS 平台的藍綠部署

在藍綠部署(Blue‑Green Deployment)中,兩套相同的服務同時運行,分別指向不同的環境變數(例如不同的資料庫連線)。只要在 docker-compose 或 Kubernetes 的 ConfigMap 中切換 NODE_ENV,即可讓新版本自動使用 production 設定,而舊版本仍保留 development 設定供測試。

2. 需要即時偵錯的 Staging 環境

Staging 常需要 開發者除錯,但又不希望影響正式流量。可以在 staging 環境自訂一個 NODE_ENV=staging,在 config 中繼承 production 再加上 enableMorgan: truelogLevel: "debug",即兼顧效能與除錯。

3. 伺服器less(如 Vercel、Netlify)上部署 Express

這類平台會自動設定 NODE_ENV=production,但仍可在 vercel.json 中加入自訂環境變數,並在程式碼中使用 process.env.VERCEL_URL 來動態決定 CORS 設定,確保開發與正式環境的差異不會造成跨域錯誤。


總結

  • 環境分離 是提升 ExpressJS(配合 TypeScript)應用可維護性與安全性的關鍵。
  • 透過 NODE_ENV.env、模組化設定檔,我們可以在同一套程式碼中自動切換 開發測試正式 的行為。
  • 依環境載入 logger、helmet、compression 等中介軟體,讓開發時資訊完整、正式時效能與安全兼備。
  • 注意 敏感資訊外部化錯誤訊息遮蔽,以及 Docker/K8s 中的環境變數設定,避免常見的資安與效能陷阱。

只要掌握以上概念與實作範例,你就能在任何部署平台上,快速切換環境、保持程式碼乾淨,並確保 Production 具備最佳的效能與安全性。祝開發順利,部署無慮!