本文 AI 產出,尚未審核

ExpressJS (TypeScript) – RESTful API 設計

主題:HTTP Methods 概念


簡介

在設計 RESTful API 時,最核心的概念莫過於 HTTP 方法(HTTP Methods)。它決定了客戶端與伺服器之間的意圖與行為,讓 API 能夠遵循統一的資源操作規範。若對 HTTP 方法的意義與使用方式不清楚,常會導致 API 難以維護、文件不一致,甚至安全漏洞。

在 ExpressJS 搭配 TypeScript 的開發環境中,正確使用 GETPOSTPUTPATCHDELETE 等方法,不僅能提升程式碼可讀性,也有助於自動產生型別安全的路由定義。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整掌握 HTTP 方法在 Express+TS 專案中的應用。


核心概念

1. HTTP 方法的語意

方法 語意 常見用途
GET 取得資源,不應改變伺服器狀態 查詢清單、取得單筆資料
POST 建立新資源或觸發非冪等操作 新增使用者、上傳檔案
PUT 完全取代目標資源(冪等) 更新整筆資料
PATCH 部分更新目標資源(非完全取代) 修改使用者的部份欄位
DELETE 刪除資源(冪等) 移除文章、刪除帳號
OPTIONS 取得資源支援的 HTTP 方法 CORS 預檢請求
HEAD 只取得標頭資訊,不回傳主體 快速檢查資源是否存在

冪等(Idempotent)指同一個請求重複執行多次,結果與執行一次相同。GETPUTDELETE 通常是冪等的,而 POST 則不是。


2. 在 Express+TypeScript 中宣告路由

Express 的路由函式接受兩個必填參數:路徑(string)與 處理函式RequestHandler)。在 TypeScript 中,我們會先為請求與回應加上型別,確保參數的正確性。

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

app.use(express.json()); // 解析 JSON body

3. 程式碼範例

以下示範 5 個常見的 HTTP 方法應用,並在每段程式碼中加入說明註解。

3.1 GET – 取得全部使用者

// 取得使用者清單,支援 query string 篩選
app.get('/api/users', (req: Request, res: Response) => {
  const { role } = req.query; // 例如 /api/users?role=admin
  // 假設有一個 UserService 提供資料存取
  const users = UserService.findAll(role as string | undefined);
  res.json(users);
});

重點GET 請求不應有副作用,僅使用 req.query 取得過濾條件。


3.2 POST – 新增使用者

// 建立新使用者,回傳 201 Created 並附上 Location Header
app.post('/api/users', (req: Request, res: Response) => {
  const newUser = req.body; // 需要在前面使用 app.use(express.json())
  const created = UserService.create(newUser);
  // 依照 REST 原則,回傳新資源的 URL
  res.status(201)
     .location(`/api/users/${created.id}`)
     .json(created);
});

技巧res.location() 能自動產生 Location 標頭,讓客戶端知道新資源的位址。


3.3 PUT – 完全更新使用者

// 完全取代指定 ID 的使用者資料
app.put('/api/users/:id', (req: Request, res: Response) => {
  const { id } = req.params;
  const updatedData = req.body;
  const updated = UserService.replace(id, updatedData);
  if (!updated) {
    return res.sendStatus(404); // 找不到資源
  }
  res.json(updated);
});

注意PUT 必須提供完整的資源表示,缺少的欄位會被視為 null預設值


3.4 PATCH – 部分更新使用者

// 只修改提供的欄位
app.patch('/api/users/:id', (req: Request, res: Response) => {
  const { id } = req.params;
  const partialData = req.body;
  const updated = UserService.updatePartial(id, partialData);
  if (!updated) {
    return res.sendStatus(404);
  }
  res.json(updated);
});

說明PATCH 常與 JSON Patch(RFC 6902)或 JSON Merge Patch(RFC 7386)結合,讓前端只送出變更的欄位。


3.5 DELETE – 刪除使用者

// 刪除指定 ID 的使用者,回傳 204 No Content
app.delete('/api/users/:id', (req: Request, res: Response) => {
  const { id } = req.params;
  const success = UserService.delete(id);
  if (!success) {
    return res.sendStatus(404);
  }
  // 成功刪除時不回傳內容
  res.sendStatus(204);
});

提醒204 表示「成功但沒有回傳內容」,是 DELETE 的慣用回應。


4. 路由分層與型別安全

在大型專案中,我們會把路由切分成 router,並利用 TypeScript 的介面定義請求參數與回傳型別。

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

const router = Router();

router.get('/', async (req: Request, res: Response) => {
  const users: User[] = await UserService.findAll();
  res.json(users);
});

// 其他路由 (POST, PUT, PATCH, DELETE) 省略

export default router;

在主程式中掛載:

import userRouter from './routes/userRouter';
app.use('/api/users', userRouter);

這樣的結構讓 HTTP 方法 的概念與實作保持清晰,同時也方便單元測試與文件生成。


常見陷阱與最佳實踐

陷阱 為何會發生 解決方式
使用 GET 攜帶大量資料 把大量 JSON 放在 query string,會超過 URL 長度限制 改用 POST,或將資料放在請求體(body)
POST 用於非建立資源 把「執行指令」的動作放在 POST,缺乏語意 若是觸發動作且非冪等,仍可用 POST;但要在文件中說明
PUT 只提供部分欄位 造成資料遺失(未提供的欄位被清空) 若僅更新部分,使用 PATCH;或在 PUT 前先取得完整資源
忘記回傳正確的 HTTP 狀態碼 前端無法正確判斷成功或失敗 依照 RFC 7231 使用 200/201/204/400/401/403/404/500 等狀態碼
未設定 CORS 預檢(OPTIONS) 前端跨域請求被瀏覽器阻擋 使用 app.use(cors()) 或手動回應 OPTIONS

最佳實踐

  1. 語意化路由:資源使用複數名詞 (/api/users),動作由 HTTP 方法決定。
  2. 回傳一致的結構:成功回傳 { data: ..., meta: ... },錯誤回傳 { error: { code, message } }
  3. 使用 async/await 並捕捉錯誤:統一錯誤處理中介軟體 (errorHandler)。
  4. 加入驗證與授權:對敏感方法(POST、PUT、PATCH、DELETE)加上 JWT 或 OAuth 中介軟體。
  5. 文件自動化:利用 swagger-ui-express + tsdoc 產生 API 文件,讓方法與參數一目了然。

實際應用場景

  1. 商品管理系統

    • GET /api/products:列出所有商品,支援分頁與篩選。
    • POST /api/products:新增商品,回傳 201 並附上商品 URL。
    • PUT /api/products/:id:完整更新商品資訊(價格、庫存、描述)。
    • PATCH /api/products/:id:僅調整庫存或折扣。
    • DELETE /api/products/:id:下架商品,回傳 204。
  2. 部落格平台

    • GET /api/posts/:slug:根據 slug 取得文章內容(快取友好)。
    • POST /api/posts:發佈新文章,僅允許已驗證的作者。
    • PATCH /api/posts/:id:編輯標題或標籤,不影響內容。
    • DELETE /api/posts/:id:刪除文章,同時觸發關聯的評論刪除。
  3. 即時聊天服務

    • POST /api/messages:送出訊息(非冪等),回傳訊息 ID。
    • GET /api/conversations/:id/messages:取得對話記錄,支援 limitbeforeafter 參數。
    • DELETE /api/messages/:id:撤回訊息,僅允許發送者或管理員。

以上案例皆展示 HTTP 方法 如何映射到實際業務需求,讓 API 保持一致且易於擴充。


總結

  • HTTP Methods 是 RESTful 設計的基石,正確使用能讓 API 具備語意清晰、行為可預測的特性。
  • ExpressJS + TypeScript 中,我們透過型別安全的路由宣告,結合 async/await、錯誤中介軟體與 CORS 設定,使開發過程更可靠。
  • 避免常見陷阱(如使用錯誤的方法、未回傳適當狀態碼),並遵循最佳實踐(語意化路由、統一回應格式、授權驗證),即可打造高可維護、易測試的 API。
  • 最後,將 HTTP 方法映射到實際業務(商品、部落格、聊天)時,仍要根據資源的「狀態變化」與「操作冪等性」來選擇最合適的方法。

掌握了這些概念,你就能在 Express+TS 專案中設計出 乾淨、可擴充且符合標準 的 RESTful API,為前端與其他服務提供穩定的資料介面。祝開發順利!