本文 AI 產出,尚未審核

ExpressJS (TypeScript) – Routing 路由管理

主題:基本路由建立


簡介

在 Web 應用程式的開發過程中,路由 (Routing) 是負責把「使用者的請求」對應到「程式碼的處理函式」的關鍵機制。沒有良好的路由設計,API 會變得難以維護、測試與擴充。
Express 是 Node.js 生態系中最受歡迎的 Web 框架,而 TypeScript 為 JavaScript 加上了靜態類型,讓開發者在編寫路由時能即時捕捉錯誤、提升 IDE 補全與程式碼可讀性。因此,掌握 「Express + TypeScript」的基本路由建立,是進一步開發 RESTful API、SPA 後端或微服務的第一步。

本篇文章將以 清晰、實用 的方式說明如何在 TypeScript 專案中建立與管理路由,並提供多個範例、常見陷阱與最佳實踐,讓你能快速上手並在實務專案中自信運用。


核心概念

1. Express 應用與 Router 的差別

概念 app Router
目的 整個 Express 應用的入口點,負責全局中介軟體與錯誤處理 可視為「子應用」或「模組化的路由集合」
使用時機 只建立一次,通常放在 src/index.ts 需要將相關路由分組時使用,例如 users, products
範例 app.get('/status', ...) router.get('/profile', ...),再掛到 app.use('/users', router)

小技巧:在大型專案中,盡量把每個功能模組的路由寫在獨立的 router 檔案,保持 app.ts 只負責設定全局中介軟體與錯誤處理。


2. TypeScript 中的型別宣告

  • RequestResponseNextFunction:從 express 套件匯入的型別,提供完整的屬性與方法提示。
  • 自訂 Request 參數:若要在 req.paramsreq.body 等加入自訂欄位,需要使用 介面 (interface) 擴充
import { Request } from 'express';

export interface CreateUserBody {
  name: string;
  email: string;
  age?: number;
}

/* 將 body 型別套用到 Request */
export type CreateUserRequest = Request<{}, {}, CreateUserBody>;

3. 基本路由語法

router.get(path, handler);
router.post(path, handler);
router.put(path, handler);
router.delete(path, handler);
  • path:支援字串、正則表達式、或路由參數 (/users/:id)。
  • handler(req, res, next) => {},在 TypeScript 中可以給予完整型別。

程式碼範例

以下示範 5 個常見且實用的路由建立方式,每段程式碼皆附上說明與型別註解。

範例 1:最簡單的 GET 路由

// src/routes/status.ts
import { Router, Request, Response } from 'express';

const router = Router();

/**
 * 取得伺服器狀態
 * GET /status
 */
router.get('/status', (req: Request, res: Response) => {
  res.json({ ok: true, timestamp: Date.now() });
});

export default router;

說明:此路由不需要任何參數,直接回傳 JSON。使用 Router 可以在 index.ts 中掛載:app.use('/', statusRouter);


範例 2:帶參數的路由與型別安全

// src/routes/users.ts
import { Router, Request, Response, NextFunction } from 'express';

// 1. 定義參數型別
interface UserParams {
  id: string; // 由 Express 解析為字串
}

// 2. 建立 Router
const router = Router();

/**
 * 取得單一使用者資訊
 * GET /users/:id
 */
router.get('/:id', (req: Request<UserParams>, res: Response, next: NextFunction) => {
  const userId = req.params.id;
  // 假設從資料庫取得資料
  const user = { id: userId, name: 'Alice' };
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }
  res.json(user);
});

export default router;

重點:使用 Request<UserParams>req.params.id 在編譯時即被識別為 string,避免因型別錯誤導致的執行時例外。


範例 3:POST 路由與 Body 驗證(使用 class-validator

// src/routes/products.ts
import { Router, Request, Response } from 'express';
import { validate, IsString, IsNumber, IsOptional } from 'class-validator';
import { plainToInstance } from 'class-transformer';

class CreateProductDto {
  @IsString()
  name!: string;

  @IsNumber()
  price!: number;

  @IsOptional()
  @IsString()
  description?: string;
}

const router = Router();

/**
 * 新增商品
 * POST /products
 */
router.post('/', async (req: Request, res: Response) => {
  const dto = plainToInstance(CreateProductDto, req.body);
  const errors = await validate(dto);
  if (errors.length > 0) {
    return res.status(400).json({ errors });
  }

  // 這裡通常會寫入資料庫
  const created = { id: Date.now(), ...dto };
  res.status(201).json(created);
});

export default router;

說明:透過 class-validator + class-transformer,在 TypeScript 中實現 DTO(資料傳輸物件) 的驗證,讓 API 更安全、錯誤回報更友善。


範例 4:使用 Middleware 處理驗證與授權

// src/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';

/**
 * 假設的驗證中介軟體
 * 若 token 無效則回傳 401
 */
export function authGuard(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }
  const token = authHeader.split(' ')[1];
  // 這裡可放 JWT 驗證邏輯
  if (token !== 'valid-token') {
    return res.status(403).json({ error: 'Invalid token' });
  }
  // token 合法,繼續往下走
  next();
}
// src/routes/profile.ts
import { Router, Request, Response } from 'express';
import { authGuard } from '../middleware/auth';

const router = Router();

/**
 * 取得使用者個人資料(需要授權)
 * GET /profile
 */
router.get('/', authGuard, (req: Request, res: Response) => {
  // 假設從 token 解碼出使用者資訊
  const user = { id: '123', name: 'Bob' };
  res.json(user);
});

export default router;

關鍵:將授權邏輯抽離成 middleware,讓路由保持 單一責任,且可在多個路由間重複使用。


範例 5:分層路由(Nested Router)

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

/**
 * 管理員 - 取得所有使用者
 * GET /admin/users
 */
router.get('/', (req: Request, res: Response) => {
  // 假設回傳使用者清單
  res.json([{ id: '1', name: 'Alice' }, { id: '2', name: 'Bob' }]);
});

export default router;
// src/routes/admin/index.ts
import { Router } from 'express';
import usersRouter from './users';

const router = Router();

// 以 /admin 為基礎路徑,掛載子路由
router.use('/users', usersRouter);

export default router;
// src/app.ts
import express from 'express';
import statusRouter from './routes/status';
import usersRouter from './routes/users';
import adminRouter from './routes/admin';

const app = express();
app.use(express.json());

// 全局路由掛載
app.use('/', statusRouter);
app.use('/users', usersRouter);
app.use('/admin', adminRouter);

export default app;

實務意義:當應用程式規模增長,Nested Router 能讓路徑結構清晰、檔案分層易於維護。


常見陷阱與最佳實踐

陷阱 說明 最佳實踐
忘記 express.json() POST/PUT 時 req.bodyundefined app.ts 最上方加入 app.use(express.json());
路由參數未定義型別 req.params.id 會是 any,失去 TypeScript 好處 使用 Request<Params> 介面或 as const 方式明確型別
中介軟體順序錯誤 授權中介軟體放在路由之後,導致未被觸發 先掛載 中介軟體,再掛載實際路由
過度寫在同一檔 所有路由集中於 app.ts,難以維護 模組化:每個功能領域獨立 router 檔案
未處理未捕獲的錯誤 異步函式拋出例外會讓程式崩潰 使用 express-async-errors 或自行捕獲 try/catch,並傳遞給錯誤處理 middleware

進階最佳實踐

  1. 使用 class-validator + DTO:讓 API 輸入驗證與型別同步。
  2. 自訂錯誤類別:例如 NotFoundError, ValidationError,在全局錯誤中介軟體中統一回傳。
  3. 加入 Swagger (OpenAPI) 文件:使用 swagger-jsdoc + swagger-ui-express,自動產生 API 文件,提升團隊協作。
  4. 測試驅動開發 (TDD):利用 supertest 搭配 jest,為每條路由寫單元測試,確保行為不被回歸破壞。
  5. 環境變數管理:將路由前綴(如 /api/v1)放入 .env,讓不同環境的路徑保持一致。

實際應用場景

場景 需求 路由設計示例
電商平台 商品列表、下單、使用者購物車 /api/v1/products, /api/v1/orders, /api/v1/cart(分別使用 Router
部落格系統 文章 CRUD、分類、標籤、評論 /blog/posts, /blog/categories, /blog/tags, /blog/comments
行動 App 後端 使用者登入、JWT 驗證、推播設定 /auth/login, /auth/refresh, /users/me(使用 authGuard
微服務架構 每個服務只負責單一領域 服務 A:/service-a/users, 服務 B:/service-b/payments,透過 API Gateway 轉發
內部管理系統 多層級權限、資料匯出 /admin/users, /admin/reports, 使用 roleGuard 中介軟體檢查權限

案例說明:在電商平台中,商品路由會使用 分層路由/api/v1/products/:id/reviews),同時在 POST /orders 前加入 授權與驗證 中介軟體,確保只有已登入且購物車內有商品的使用者才能下單。


總結

  • 路由是 Express 應用的神經中樞,透過 Router 可以將功能模組化、維護性提升。
  • TypeScript 中,利用介面與型別參數 (Request<Params, ResBody, ReqBody>) 讓路由參數、請求體、回應體皆具備編譯時檢查,減少執行時錯誤。
  • DTO + class-validator 為輸入驗證提供一致且可重用的解決方案;middleware 則負責授權、錯誤處理與共用邏輯,使路由保持單一職責。
  • 面對大型專案,分層路由環境變數管理Swagger 文件測試 是不可或缺的配套工具。

掌握了本文的 基本路由建立 後,你就能在任何 Node.js + TypeScript 專案中,快速構建乾淨、可擴充且安全的 API。接下來可以進一步探索 路由參數驗證、動態路由、以及與資料庫的整合,讓你的後端開發之路更加順暢。祝開發愉快!