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.Request、express.Response、express.NextFunction的型別,讓編譯器能協助檢查參數使用是否正確。
2. 執行順序
Express 會 依照註冊的順序 依次執行 Middleware。每個 Middleware 必須在適當時機呼叫 next(),否則請求會卡在該層,導致超時或無回應。
app.use(mwA); // 1️⃣ 先執行
app.use(mwB); // 2️⃣ 再執行
app.get('/test', handler); // 3️⃣ 最後進入路由處理函式
如果在 mwA 中直接回傳回應(例如 res.send()),後續的 mwB 與 handler 就不會被觸發。
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 精簡,每個只負責單一職責(單一職責原則) |
最佳實踐:
- 使用 TypeScript 型別:為
req.body、req.params、req.query定義介面,提升開發安全性。 - 將通用功能抽成模組:例如
logger.ts、authGuard.ts,方便在多個專案間共享。 - 在測試環境下驗證 Middleware 的順序:可以使用
supertest搭配 Jest 撰寫整合測試,確保next()的呼叫與回應行為符合預期。 - 避免在 Middleware 中直接操作資料庫(除非是必要的認證/授權),以免影響請求的延遲。
- 使用
express-async-errors(或自行捕捉 async 錯誤)讓asyncMiddleware 的拋錯自動傳給錯誤處理 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 是 Express 與 TypeScript 結合的核心機制,透過 請求前置處理、回應後置處理、以及 錯誤捕捉,讓開發者能以 模組化、可重用 的方式建構大型 Web 應用。掌握以下要點,就能在實務專案中游刃有餘:
- 了解函式簽名
(req, res, next),正確使用next()。 - 依照需求選擇全域、路由層級或錯誤處理 Middleware。
- 善用 TypeScript 型別,在編譯期即捕捉錯誤。
- 遵守最佳實踐:單一職責、錯誤傳遞、測試驗證。
- 根據實際場景(認證、CORS、速率限制…)選擇適當的 Middleware 組合。
只要把握這些概念與實作技巧,你就能在 Express + TypeScript 的開發環境中,快速構建安全、可維護且易於擴充的服務。祝開發順利!