ExpressJS (TypeScript) – RESTful API 設計
主題:HTTP Methods 概念
簡介
在設計 RESTful API 時,最核心的概念莫過於 HTTP 方法(HTTP Methods)。它決定了客戶端與伺服器之間的意圖與行為,讓 API 能夠遵循統一的資源操作規範。若對 HTTP 方法的意義與使用方式不清楚,常會導致 API 難以維護、文件不一致,甚至安全漏洞。
在 ExpressJS 搭配 TypeScript 的開發環境中,正確使用 GET、POST、PUT、PATCH、DELETE 等方法,不僅能提升程式碼可讀性,也有助於自動產生型別安全的路由定義。本文將從概念說明、實作範例、常見陷阱與最佳實踐,帶你完整掌握 HTTP 方法在 Express+TS 專案中的應用。
核心概念
1. HTTP 方法的語意
| 方法 | 語意 | 常見用途 |
|---|---|---|
| GET | 取得資源,不應改變伺服器狀態 | 查詢清單、取得單筆資料 |
| POST | 建立新資源或觸發非冪等操作 | 新增使用者、上傳檔案 |
| PUT | 完全取代目標資源(冪等) | 更新整筆資料 |
| PATCH | 部分更新目標資源(非完全取代) | 修改使用者的部份欄位 |
| DELETE | 刪除資源(冪等) | 移除文章、刪除帳號 |
| OPTIONS | 取得資源支援的 HTTP 方法 | CORS 預檢請求 |
| HEAD | 只取得標頭資訊,不回傳主體 | 快速檢查資源是否存在 |
冪等(Idempotent)指同一個請求重複執行多次,結果與執行一次相同。
GET、PUT、DELETE通常是冪等的,而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 |
最佳實踐
- 語意化路由:資源使用複數名詞 (
/api/users),動作由 HTTP 方法決定。 - 回傳一致的結構:成功回傳
{ data: ..., meta: ... },錯誤回傳{ error: { code, message } }。 - 使用
async/await並捕捉錯誤:統一錯誤處理中介軟體 (errorHandler)。 - 加入驗證與授權:對敏感方法(POST、PUT、PATCH、DELETE)加上 JWT 或 OAuth 中介軟體。
- 文件自動化:利用
swagger-ui-express+tsdoc產生 API 文件,讓方法與參數一目了然。
實際應用場景
商品管理系統
GET /api/products:列出所有商品,支援分頁與篩選。POST /api/products:新增商品,回傳 201 並附上商品 URL。PUT /api/products/:id:完整更新商品資訊(價格、庫存、描述)。PATCH /api/products/:id:僅調整庫存或折扣。DELETE /api/products/:id:下架商品,回傳 204。
部落格平台
GET /api/posts/:slug:根據 slug 取得文章內容(快取友好)。POST /api/posts:發佈新文章,僅允許已驗證的作者。PATCH /api/posts/:id:編輯標題或標籤,不影響內容。DELETE /api/posts/:id:刪除文章,同時觸發關聯的評論刪除。
即時聊天服務
POST /api/messages:送出訊息(非冪等),回傳訊息 ID。GET /api/conversations/:id/messages:取得對話記錄,支援limit、before、after參數。DELETE /api/messages/:id:撤回訊息,僅允許發送者或管理員。
以上案例皆展示 HTTP 方法 如何映射到實際業務需求,讓 API 保持一致且易於擴充。
總結
- HTTP Methods 是 RESTful 設計的基石,正確使用能讓 API 具備語意清晰、行為可預測的特性。
- 在 ExpressJS + TypeScript 中,我們透過型別安全的路由宣告,結合
async/await、錯誤中介軟體與 CORS 設定,使開發過程更可靠。 - 避免常見陷阱(如使用錯誤的方法、未回傳適當狀態碼),並遵循最佳實踐(語意化路由、統一回應格式、授權驗證),即可打造高可維護、易測試的 API。
- 最後,將 HTTP 方法映射到實際業務(商品、部落格、聊天)時,仍要根據資源的「狀態變化」與「操作冪等性」來選擇最合適的方法。
掌握了這些概念,你就能在 Express+TS 專案中設計出 乾淨、可擴充且符合標準 的 RESTful API,為前端與其他服務提供穩定的資料介面。祝開發順利!