本文 AI 產出,尚未審核

ExpressJS (TypeScript)

Middleware 概念與使用 — 什麼是 Middleware


簡介

Node.js 生態系統中,Express 是最常被採用的 Web 框架之一。它之所以能快速構建 API、網站,核心祕密就在於 Middleware 機制。Middleware 讓開發者可以在請求(request)與回應(response)之間插入自訂的處理流程,從驗證、日誌、錯誤處理到資料轉換,都能以模組化的方式重複使用。

對於 TypeScript 使用者而言,Middleware 不僅保留了 JavaScript 的彈性,還能透過型別系統提前捕捉錯誤、提升 IDE 自動補完的體驗。因此,掌握 Middleware 的概念與正確寫法,是在 Express 上開發可維護、可擴充系統的必備功力。


核心概念

1. Middleware 的基本定義

在 Express 中,Middleware 本質上是一個函式,簽名為:

(req: Request, res: Response, next: NextFunction) => void | Promise<void>
  • req: 代表 HTTP 請求物件,包含 URL、參數、標頭等資訊。
  • res: 代表 HTTP 回應物件,用來寫入資料、設定狀態碼等。
  • next: 呼叫後續 Middleware 的函式,若不呼叫則請求會在此中斷。

注意:在 TypeScript 中,建議使用 express.Requestexpress.Responseexpress.NextFunction 的型別,讓編譯器能協助檢查參數使用是否正確。

2. 執行順序

Express 會 依照註冊的順序 依次執行 Middleware。每個 Middleware 必須在適當時機呼叫 next(),否則請求會卡在該層,導致超時或無回應。

app.use(mwA);   // 1️⃣ 先執行
app.use(mwB);   // 2️⃣ 再執行
app.get('/test', handler); // 3️⃣ 最後進入路由處理函式

如果在 mwA 中直接回傳回應(例如 res.send()),後續的 mwBhandler 就不會被觸發。

3. 常見類型的 Middleware

類型 目的 典型範例
全域 Middleware 針對所有路由執行;常用於日誌、CORS、body 解析等 app.use(express.json())
路由層級 Middleware 只對特定路徑或 HTTP 方法生效;適合驗證或權限檢查 router.post('/admin', authMw, handler)
錯誤處理 Middleware 捕捉前面 Middleware/路由拋出的錯誤;必須有四個參數 (err, req, res, next) app.use(errorHandler)
內建 Middleware Express 已提供的工具函式 express.static('public')

程式碼範例

以下示範 4 個實用的 Middleware,全部以 TypeScript 撰寫,並加入詳細註解說明。

1️⃣ 日誌 Middleware(全域)

import { Request, Response, NextFunction } from 'express';

/**
 * 記錄每一次請求的 method、url 與時間戳記
 */
export const logger = (req: Request, res: Response, next: NextFunction): void => {
  const now = new Date().toISOString();
  console.log(`[${now}] ${req.method} ${req.originalUrl}`);
  // 必須呼叫 next(),讓請求繼續往下走
  next();
};

使用方式:

import express from 'express';
import { logger } from './middleware/logger';

const app = express();
app.use(logger); // 於所有路由前先記錄日誌

2️⃣ JSON Body 解析(內建)

import express from 'express';

const app = express();

// 內建的 bodyParser,會把 JSON 文字自動轉成物件放到 req.body
app.use(express.json());

提示:若要支援 application/x-www-form-urlencoded,可再加入 express.urlencoded({ extended: true })

3️⃣ 權限驗證 Middleware(路由層級)

import { Request, Response, NextFunction } from 'express';

/**
 * 檢查 Authorization Header 是否存在且符合簡易 token 格式
 */
export const authGuard = (req: Request, res: Response, next: NextFunction): void => {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    // 直接回傳 401,之後的 Middleware 不會被執行
    return res.status(401).json({ message: '未授權或 Token 格式錯誤' });
  }

  const token = authHeader.split(' ')[1];
  // 這裡僅示範,實務上應該使用 JWT 驗證
  if (token !== 'my-secret-token') {
    return res.status(403).json({ message: '權限不足' });
  }

  // 驗證通過,繼續執行後面的處理
  next();
};

掛在特定路由上:

import express from 'express';
import { authGuard } from './middleware/authGuard';

const router = express.Router();

router.get('/profile', authGuard, (req, res) => {
  res.json({ user: 'John Doe', role: 'admin' });
});

app.use('/api', router);

4️⃣ 錯誤處理 Middleware(全域)

import { Request, Response, NextFunction } from 'express';

/**
 * 捕捉所有未被處理的錯誤,統一回傳 JSON 格式
 * 注意:必須有四個參數,才能被 Express 辨識為錯誤 Middleware
 */
export const errorHandler = (
  err: any,
  _req: Request,
  res: Response,
  _next: NextFunction
): void => {
  console.error('❌ 錯誤發生:', err);
  const status = err.status ?? 500;
  const message = err.message ?? '伺服器內部錯誤';
  res.status(status).json({ error: message });
};

註冊方式(通常放在所有路由之後):

app.use(errorHandler);

常見陷阱與最佳實踐

陷阱 可能的後果 解決方式 / 最佳實踐
忘記呼叫 next() 請求卡住,客戶端永遠不會收到回應 務必在非終止回應的 Middleware 最後呼叫 next()
在全域 Middleware 中寫入回應 會阻斷後續路由,導致功能失效 僅在需要「提前終止」的情況下(例如認證失敗)才回傳
錯誤傳遞不當 錯誤無法被統一處理,容易產生 500 隱性錯誤 使用 next(err) 把錯誤傳給錯誤處理 Middleware
同步函式拋出例外 Express 無法捕捉,導致程式直接崩潰 把可能拋錯的程式包在 try/catch,或改寫成 async/await 並使用 next(err)
過度堆疊 Middleware 執行流程難以追蹤、效能下降 保持 Middleware 精簡,每個只負責單一職責(單一職責原則)

最佳實踐

  1. 使用 TypeScript 型別:為 req.bodyreq.paramsreq.query 定義介面,提升開發安全性。
  2. 將通用功能抽成模組:例如 logger.tsauthGuard.ts,方便在多個專案間共享。
  3. 在測試環境下驗證 Middleware 的順序:可以使用 supertest 搭配 Jest 撰寫整合測試,確保 next() 的呼叫與回應行為符合預期。
  4. 避免在 Middleware 中直接操作資料庫(除非是必要的認證/授權),以免影響請求的延遲。
  5. 使用 express-async-errors(或自行捕捉 async 錯誤)讓 async Middleware 的拋錯自動傳給錯誤處理 Middleware。

實際應用場景

場景 典型 Middleware 為什麼需要
API 金鑰驗證 apiKeyGuard(檢查 query 或 header) 防止未授權的外部呼叫
跨域資源共享 (CORS) cors()(第三方套件) 讓前端應用能安全存取 API
請求速率限制 (Rate Limiting) express-rate-limit 防止惡意刷流量、保護資源
檔案上傳前的檔案類型檢查 multer + 自訂檢查 確保只接受合法檔案,避免安全漏洞
多語系錯誤訊息 i18nMiddleware(根據 Accept-Language 轉譯) 提升使用者體驗,符合國際化需求

範例:若要在所有 /admin/** 路由前加入權限檢查,只需要:

app.use('/admin', authGuard, adminRouter);

這樣 authGuard 只會在 /admin 相關請求觸發,保持其他路由的效能。


總結

Middleware 是 ExpressTypeScript 結合的核心機制,透過 請求前置處理回應後置處理、以及 錯誤捕捉,讓開發者能以 模組化、可重用 的方式建構大型 Web 應用。掌握以下要點,就能在實務專案中游刃有餘:

  1. 了解函式簽名 (req, res, next),正確使用 next()
  2. 依照需求選擇全域、路由層級或錯誤處理 Middleware
  3. 善用 TypeScript 型別,在編譯期即捕捉錯誤。
  4. 遵守最佳實踐:單一職責、錯誤傳遞、測試驗證。
  5. 根據實際場景(認證、CORS、速率限制…)選擇適當的 Middleware 組合。

只要把握這些概念與實作技巧,你就能在 Express + TypeScript 的開發環境中,快速構建安全、可維護且易於擴充的服務。祝開發順利!