iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Modern Web

現在就學Node.js系列 第 12

Express.js 中間件 (Middleware) - Day12

  • 分享至 

  • xImage
  •  

Middleware 是什麼?

在 Express 中,Middleware 就是「請求與回應之間的過濾層」

它會攔截進來的請求,進行驗證、轉換或紀錄,然後決定是否繼續交給下一層處理。

Middleware 的函式格式是固定的:

(req, res, next) => { ... }

三個核心角色:

  • req → 使用者送來的請求物件 (Request)
  • res → 伺服器要回應的回應物件 (Response)
  • next → 呼叫後才會進入下一個中間件

如果忘記呼叫 next(),請求就會停在這一層,導致「卡死」。

Middleware 的運作

假設「請求」就像是一顆球,而每一層 中間件 (Middleware) 就像是一段連接管道。
這顆球(請求)要到達最終的處理器(Route Handler),必須 依序通過每一段管道。
在每個管道(中間件)裡,系統會依照該中間件的邏輯進行判斷或處理,例如驗證、紀錄或轉換資料。

如果一切通過,就會被送往下一個管道。
如果某一層中間件直接回應了(例如 res.send())或阻擋了(沒有呼叫 next()),
那麼這顆球就不會再往下傳,請求也就停在這裡。

只有順利通過所有檢查,球才會抵達最終的處理器(Route Handler),完成整個請求-回應流程。

Client
   │
   ▼
Middleware A (驗證登入)
   │
   ▼
Middleware B (Body Parser)
   │
   ▼
Middleware C (Logger)
   │
   ▼
Route Handler (主要邏輯)
   │
   ▼
Error Middleware (錯誤處理)
   │
   ▼
Response 回給 Client

規則:

  • 先進先出 → 按註冊順序依序執行。
  • 短路效果 → 如果中途 res.send()res.json() 回應了,後面的中間件就不會再跑。

Middleware 的種類與範例

1. 全域中間件 (Application-level)

app.use() 註冊,會套用在所有請求上。

app.use((req, res, next) => {
  console.log(`[LOG] ${req.method} ${req.url}`);
  next();
});

2. 路由專屬中間件 (Router-level)

有時候只想在特定路由加上檢查或處理,就可以這樣寫:

function auth(req, res, next) {
  const token = req.headers.authorization;
  if (token === "Bearer token123") {
    next(); // ✅ 驗證通過
  } else {
    res.status(401).json({ error: "Unauthorized" });
  }
}

app.get("/dashboard", auth, (req, res) => {
  res.send("歡迎來到管理後台 🎉");
});

👉 /dashboard 會經過驗證,但 /about/notes 不會。

也可以一次掛多個中間件:

function logger(req, res, next) {
  console.log("⏰ Time:", Date.now());
  next();
}

function checkAuth(req, res, next) {
  if (req.headers.authorization) next();
  else res.status(401).send("請先登入");
}

app.get("/profile", [logger, checkAuth], (req, res) => {
  res.send("這是個人檔案頁面 👤");
});

3. 內建中間件

Express 提供一些實用的內建中間件:

app.use(express.json());        // 處理 JSON body
app.use(express.urlencoded());  // 處理表單
app.use(express.static("public")); // 提供靜態檔案

4. 第三方中間件

安裝套件,擴充功能:

  • morgan → 請求日誌
  • cors → 處理跨來源請求
  • helmet → 增加安全性標頭
import morgan from "morgan";
import cors from "cors";

app.use(morgan("dev"));
app.use(cors());

5. 錯誤處理中間件

Express 有專屬的「錯誤處理器」,格式必須是四個參數 (err, req, res, next)

app.use((err, req, res, next) => {
  console.error("❌ 發生錯誤:", err.message);
  res.status(500).json({ error: "伺服器錯誤" });
});

它的角色是「最後防線」,統一處理應用程式的例外,避免伺服器直接掛掉或使用者無限等待。

為什麼 async/await 的錯誤不會自動捕捉?

Express 不會自動捕捉 async/await 的錯誤。

如果你在路由裡忘了加 try/catch,一旦出錯,請求就會卡住,永遠不會進到錯誤中間件。

❌ 問題範例

app.get("/user", async (req, res) => {
  const data = await db.findUser(); // 如果這裡出錯 → 卡死
  res.json(data);
});

✅ 解法 1:用 try/catch 包裝

app.get("/user", async (req, res, next) => {
  try {
    const data = await db.findUser();
    res.json(data);
  } catch (err) {
    next(err); // 交給錯誤處理中間件
  }
});

✅ 解法 2:建立 asyncHandler 包裝器

function asyncHandler(fn) {
  return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
}

app.get("/user", asyncHandler(async (req, res) => {
  const data = await db.findUser();
  res.json(data);
}));

常見應用場景 與常見開發狀況對照

以往在開發過程中常會遇到一些狀況,

因此整理成「✅ 正確 vs ❌ 錯誤」示範:

1. Body Parser

✅ 正確:

app.use(express.json()); // 放在路由前
app.post("/api/echo", (req, res) => res.json({ received: req.body }));

❌ 錯誤:

app.post("/api/echo", (req, res) => res.json({ received: req.body })); // req.body 永遠 undefined
app.use(express.json()); // 放錯順序

2. 忘記加上 next()

✅ 正確:

app.use((req, res, next) => {
  console.log(`[LOG] ${req.method} ${req.url}`);
  next();
});

❌ 錯誤:

app.use((req, res, next) => {
  console.log(`[LOG] ${req.method} ${req.url}`);
  // 忘記 next() → 請求卡住
});

3. Rate Limit (防濫用)

✅ 正確:

import rateLimit from "express-rate-limit";
const limiter = rateLimit({ windowMs: 60000, max: 5 });  //每分鐘 最多請求五次
app.use("/api/", limiter);
// 沒有限制 → API 可能被暴力攻擊

4. 錯誤處理 + async/await

✅ 正確:

app.get("/user", asyncHandler(async (req, res) => {
  const data = await db.findUser();
  res.json(data);
}));

❌ 錯誤:

app.get("/user", async (req, res) => {
  const data = await db.findUser(); // 出錯不會被捕捉
  res.json(data);
});

小結

在 Express 中,所有請求都必須經過一層層的 Middleware,就像關卡一樣,決定要不要放行、要不要轉換資料,最後才交給路由處理。

  • 分類清楚
    • 全域中間件 → 每個請求都會經過,例如日誌、body parser。
    • 路由專屬中間件 → 只針對特定路由生效,像是會員區的權限驗證。
    • 內建中間件express.json()express.static(),最常用也最基礎。
    • 第三方中間件 → 直接裝套件就能用,例如 morgancorshelmet
    • 錯誤處理中間件 → Express 的最後防線,避免錯誤讓程式卡死。
  • 常見陷阱
    • 忘記呼叫 next() → 請求會卡死。
    • 中間件順序錯誤 → 例如 express.json() 要在使用 req.body 之前。
    • async/await 沒有捕捉例外 → 建議用 asyncHandler 包裝,否則錯誤不會進錯誤處理層。

上一篇
Express.js 入門 - Day11
下一篇
RESTful API 設計 - Day13
系列文
現在就學Node.js15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言