本文 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.params、req.query 皆為 string,直接當數字使用會出錯。 |
在取得後立即使用 Number()、parseInt(),並檢查 isNaN。 |
| 未驗證必填參數 | 直接使用 req.params.id,若缺少會產生 undefined 錯誤。 |
使用 Joi、zod 或手寫驗證,回傳 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,並在路由函式內斷言。 |
額外建議
- 統一錯誤回應格式:建立一個錯誤中介層,將所有參數驗證失敗集中處理。
- 使用
express-validator:結合 TypeScript,寫出可讀性高的驗證鏈。 - 記錄日誌:對於重要的參數(如
userId、orderId),在 log 中保留,以便除錯與審計。
實際應用場景
電商商品搜尋
- 路由:
GET /products/:category - Query:
?priceMin=100&priceMax=5000&sort=price_desc - 透過路由參數定位類別,並用 query 進行價格範圍與排序的條件篩選。
- 路由:
會員資料編輯
- 路由:
PUT /users/:userId - Body:JSON 包含更新欄位。
userId由路由參數提供,確保只更新指定使用者。
- 路由:
多頁分頁 API
- 路由:
GET /articles - Query:
?page=3&pageSize=20&author=alice - 以 query 控制分頁與作者過濾,維持單一路由即可支援多種查詢需求。
- 路由:
動態匯出報表
- 路由:
GET /reports/:type(type 可能是sales、inventory) - Query:
?start=2024-01-01&end=2024-01-31&format=excel - 報表類型由路由決定,時間範圍與輸出格式則放在 query 中。
- 路由:
總結
- Route Parameters 讓 URL 本身成為資料的載體,適合用來定位唯一資源(如
id、category)。 - Query String 則是描述「條件」或「篩選」的最佳位置,保持路由結構的簡潔。
- 在 TypeScript 環境下,務必把
req.params、req.query轉成正確型別,並加入必要的驗證,以避免跑時錯誤。 - 使用 Router 模組化、正則限制參數格式、統一錯誤回應與日誌,是提升專案可維護性的關鍵。
掌握上述概念與實作技巧,你就能在 ExpressJS + TypeScript 的專案中,靈活且安全地處理各種路由需求,讓 API 更具表現力與可測試性。祝開發順利!