本文 AI 產出,尚未審核

ExpressJS (TypeScript) – Routing 路由管理

主題:Route Parameters 與 Query String


簡介

Web API網站 開發過程中,最常見的需求之一就是根據不同的 URL 取得對應的資料。
ExpressJS 提供了簡潔且彈性的路由機制,而 Route Parameters(路由參數)與 Query String(查詢字串)正是實作「動態路由」的兩大核心工具。

  • Route Parameters 讓路徑本身可以變化,例如 /users/:id 能直接抓到 id
  • Query String 則是把額外的條件放在 ? 後面,例如 /search?keyword=node&page=2

掌握這兩者的寫法與最佳實踐,能讓你的 API 更具可讀性、可維護性,也更容易與前端或第三方服務整合。本文將以 TypeScript 為基礎,說明概念、提供實作範例,並指出常見陷阱與最佳做法,讓你在 Express 專案中自信使用路由參數與查詢字串。


核心概念

1. Route Parameters 基礎

在 Express 中,路由參數使用冒號 : 作為佔位符,匹配任何單一段路徑。
TypeScript 的型別系統可以協助我們在取值時得到正確的類型提示。

import express, { Request, Response } from 'express';
const app = express();

// GET /users/:userId
app.get('/users/:userId', (req: Request, res: Response) => {
  // 透過 req.params 取得路由參數
  const { userId } = req.params; // userId 為 string
  // 這裡可以加入型別驗證或轉型
  const id = Number(userId);
  if (isNaN(id)) {
    return res.status(400).json({ error: 'Invalid userId' });
  }
  // 假設有一個 getUserById 的服務
  // const user = await getUserById(id);
  res.json({ message: `User ID is ${id}` });
});

重點

  • req.params 的屬性皆為 string,若需要數字或布林值必須自行轉型。
  • 若路由中有多個參數,req.params 會以物件形式一次返回全部。

2. 多層路由參數與正則表達式

Express 允許在同一條路由中使用多個參數,或使用正則表達式限制參數格式。

// GET /products/:category/:productId([0-9]+)
app.get('/products/:category/:productId([0-9]+)', (req, res) => {
  const { category, productId } = req.params;
  // productId 已被正則限制為只接受數字
  res.json({ category, productId: Number(productId) });
});

說明

  • :productId([0-9]+) 僅匹配由數字組成的字串,避免不必要的驗證程式碼。
  • 正則寫法可放在任何參數後面,提升路由的「自我防護」能力。

3. Query String 的取得

Query String 以 ? 開頭,鍵值對之間以 & 分隔。Express 會自動解析並放入 req.query

// GET /search?keyword=node&limit=10&page=2
app.get('/search', (req, res) => {
  // req.query 的屬性預設為 string | string[] | undefined
  const { keyword, limit = '20', page = '1' } = req.query as {
    keyword?: string;
    limit?: string;
    page?: string;
  };

  // 轉型為數字
  const limitNum = Number(limit);
  const pageNum = Number(page);

  // 基本驗證
  if (!keyword) {
    return res.status(400).json({ error: 'keyword is required' });
  }

  // 假設有搜尋服務
  // const results = await search(keyword, { limit: limitNum, page: pageNum });
  res.json({ keyword, limit: limitNum, page: pageNum });
});

小技巧

  • 使用 as 斷言將 req.query 轉成具體型別,讓 TypeScript 能提供自動完成與錯誤檢查。
  • 若預期某個參數可能是陣列(例如 ?tag=js&tag=node),req.query.tag 會是 string[],需自行判斷。

4. 結合 Route Parameters 與 Query String

在實務上,常會同時使用兩者來描述「資源」與「條件」。

// GET /orders/:orderId/items?status=shipped
app.get('/orders/:orderId/items', (req, res) => {
  const { orderId } = req.params;
  const { status } = req.query as { status?: string };

  // 只回傳符合 status 的項目
  // const items = await getOrderItems(Number(orderId), { status });
  res.json({ orderId: Number(orderId), filterStatus: status ?? 'all' });
});

5. 使用 Router 模組化路由

為了讓程式碼保持乾淨,建議把相關路由抽離成 Router

// src/routes/userRouter.ts
import { Router, Request, Response } from 'express';
const router = Router();

// 取得單一使用者
router.get('/:id', (req: Request, res: Response) => {
  const id = Number(req.params.id);
  res.json({ id });
});

// 搜尋使用者,使用 query string
router.get('/', (req: Request, res: Response) => {
  const { name, age } = req.query as { name?: string; age?: string };
  res.json({ name, age: age ? Number(age) : undefined });
});

export default router;

// 主程式
import userRouter from './routes/userRouter';
app.use('/users', userRouter);

好處

  • 每個資源的路由集中管理,易於維護。
  • 只要在 app.use 時指定基礎路徑,子路由的參數自動相對於基礎路徑。

常見陷阱與最佳實踐

陷阱 說明 最佳做法
參數型別未轉換 req.paramsreq.query 皆為 string,直接當數字使用會出錯。 在取得後立即使用 Number()parseInt(),並檢查 isNaN
未驗證必填參數 直接使用 req.params.id,若缺少會產生 undefined 錯誤。 使用 Joizod 或手寫驗證,回傳 400 錯誤訊息。
Query String 被意外陣列化 ?tag=js&tag=node 會產生 string[],若程式只接受 string 會拋錯。 判斷 Array.isArray(value),必要時使用 value[0] 或合併陣列。
路由衝突 定義 /users/:id/users/profile 會產生衝突,因 :id 會捕獲 profile 把靜態路由放在動態路由前,或使用正則限制參數形式。
過度依賴全域 any 在 TypeScript 中直接把 req.query 設為 any,失去型別安全。 以介面或型別別名描述 query,並在路由函式內斷言。

額外建議

  1. 統一錯誤回應格式:建立一個錯誤中介層,將所有參數驗證失敗集中處理。
  2. 使用 express-validator:結合 TypeScript,寫出可讀性高的驗證鏈。
  3. 記錄日誌:對於重要的參數(如 userIdorderId),在 log 中保留,以便除錯與審計。

實際應用場景

  1. 電商商品搜尋

    • 路由:GET /products/:category
    • Query:?priceMin=100&priceMax=5000&sort=price_desc
    • 透過路由參數定位類別,並用 query 進行價格範圍與排序的條件篩選。
  2. 會員資料編輯

    • 路由:PUT /users/:userId
    • Body:JSON 包含更新欄位。
    • userId 由路由參數提供,確保只更新指定使用者。
  3. 多頁分頁 API

    • 路由:GET /articles
    • Query:?page=3&pageSize=20&author=alice
    • 以 query 控制分頁與作者過濾,維持單一路由即可支援多種查詢需求。
  4. 動態匯出報表

    • 路由:GET /reports/:type(type 可能是 salesinventory
    • Query:?start=2024-01-01&end=2024-01-31&format=excel
    • 報表類型由路由決定,時間範圍與輸出格式則放在 query 中。

總結

  • Route Parameters 讓 URL 本身成為資料的載體,適合用來定位唯一資源(如 idcategory)。
  • Query String 則是描述「條件」或「篩選」的最佳位置,保持路由結構的簡潔。
  • TypeScript 環境下,務必把 req.paramsreq.query 轉成正確型別,並加入必要的驗證,以避免跑時錯誤。
  • 使用 Router 模組化、正則限制參數格式、統一錯誤回應與日誌,是提升專案可維護性的關鍵。

掌握上述概念與實作技巧,你就能在 ExpressJS + TypeScript 的專案中,靈活且安全地處理各種路由需求,讓 API 更具表現力與可測試性。祝開發順利!