結論先講
API 是前後端的合約。合約寫得好,大家照著走就不太會出事;寫得爛,每次串接都是一場災難。
好的 API 有三個核心原則:可預測(看到 URL 就知道做什麼)、一致(錯誤格式、分頁方式全站統一)、好除錯(回傳足夠的資訊讓人知道哪裡出錯)。
這篇整理了 12 個 API 設計的檢查點,不管你做 REST、GraphQL 還是 gRPC,概念都通用(但範例以 REST 為主)。
體檢清單
1. RESTful 規範
REST 不是宗教,不用死守每條教義,但基本原則要遵守:
# 好的
GET /api/users # 取得列表
GET /api/users/123 # 取得單筆
POST /api/users # 建立
PUT /api/users/123 # 完整更新
PATCH /api/users/123 # 部分更新
DELETE /api/users/123 # 刪除
# 不好的
GET /api/getUsers
POST /api/createUser
POST /api/deleteUser/123
GET /api/user/update?id=123&name=foo
- 用名詞不用動詞(資源導向)
- 複數形式(
/users不是/user) - 巢狀資源有限制深度(最多兩層)
- 動作用 HTTP method 表達
2. 統一錯誤格式
全站所有 API 的錯誤回應長一樣。 這是最容易被忽略但影響最大的設計。
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "User with ID 123 not found",
"details": [],
"requestId": "req_abc123"
}
}{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request body",
"details": [
{ "field": "email", "message": "must be a valid email" },
{ "field": "age", "message": "must be >= 0" }
],
"requestId": "req_def456"
}
}- 錯誤有 machine-readable code(不是只有 message)
- 驗證錯誤列出每個欄位的問題
- 每個 request 有唯一 ID(方便 debug)
3. 分頁策略
| 方式 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| Offset | 簡單、可跳頁 | 資料變動時會重複/遺漏 | 後台管理、資料穩定 |
| Cursor | 效能好、不會遺漏 | 不能跳頁 | 動態 feed、大量資料 |
| Keyset | 效能最好 | 實作稍複雜 | 高效能需求 |
// Cursor-based pagination 回應
{
"data": [...],
"pagination": {
"hasNext": true,
"hasPrev": false,
"nextCursor": "eyJpZCI6MTAwfQ==",
"totalCount": 1523
}
}- 有分頁(不要一次回傳全部資料)
- 回傳
totalCount(或至少hasNext) - 預設 page size 合理(20-50)
- 有 max page size 限制
4. Filtering / Sorting
GET /api/products?category=electronics&price_min=100&price_max=500
GET /api/products?sort=-created_at,name
GET /api/users?search=terry&role=admin
- 篩選用 query parameter
- 排序用
sort參數(-前綴代表降序) - 全文搜尋用
search或q參數
5. Versioning 策略
| 方式 | 範例 | 優點 | 缺點 |
|---|---|---|---|
| URL Path | /api/v1/users | 最直觀 | URL 會變 |
| Header | Accept: application/vnd.api+json;version=1 | URL 不變 | 不好測試 |
| Query | /api/users?version=1 | 簡單 | 不夠正式 |
- 有版本策略(推薦 URL path)
- 舊版本有 deprecation 通知
- breaking change 才升版號
6. Idempotency(冪等性)
同一個請求送兩次,結果應該一樣。
# 冪等的
GET /users/123 → 一定拿到同一個 user
PUT /users/123 → 結果一樣
DELETE /users/123 → 第二次回 404,但沒有副作用
# 不冪等的
POST /users → 會建立兩個 user(除非你處理了)
- PUT、DELETE 是冪等的
- POST 需要防重複(Idempotency-Key header)
- 支付相關 API 一定要有冪等機制
# Stripe 風格的 Idempotency Key
POST /api/payments
Idempotency-Key: key_unique_1237. Rate Limiting Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 67
X-RateLimit-Reset: 1710500000
Retry-After: 30
- 回傳 rate limit 資訊
- 429 回應包含
Retry-After - 不同 API 可以有不同限制
8. CORS
- 明確設定允許的 origin(不要用
*) - 只允許必要的 HTTP methods
- Preflight 回應有 cache(
Access-Control-Max-Age)
9. Authentication
- 使用 Bearer Token(
Authorization: Bearer <token>) - Token 過期時間合理
- 401 和 403 分清楚(未認證 vs 無權限)
10. API Documentation
# OpenAPI 3.0 範例
paths:
/users:
get:
summary: List all users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
responses:
'200':
description: Success
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'- 有 OpenAPI / Swagger 文件
- 每個 endpoint 有範例
- 文件跟程式碼同步(自動產生最好)
11. Response Envelope
// 統一的回應格式
{
"data": { ... },
"meta": {
"requestId": "req_abc",
"timestamp": "2026-03-15T10:00:00Z"
}
}
// 列表回應
{
"data": [...],
"pagination": { ... },
"meta": { ... }
}- 統一的回應結構
- 列表和單筆有區分
- meta 資訊(request ID、時間戳)
12. HTTP Status Codes
| 狀態碼 | 何時用 |
|---|---|
| 200 | 成功 |
| 201 | 建立成功 |
| 204 | 成功但無內容(DELETE) |
| 400 | 請求格式錯誤 |
| 401 | 未認證 |
| 403 | 無權限 |
| 404 | 資源不存在 |
| 409 | 衝突(重複建立等) |
| 422 | 驗證失敗 |
| 429 | 太多請求 |
| 500 | 伺服器錯誤 |
- 正確使用 status code(不要全部回 200)
- 不要自創 status code
- 4xx 和 5xx 有不同處理邏輯
REST vs GraphQL vs gRPC
| 特性 | REST | GraphQL | gRPC |
|---|---|---|---|
| 通訊協定 | HTTP/1.1+ | HTTP/1.1+ | HTTP/2 |
| 資料格式 | JSON | JSON | Protobuf |
| 學習曲線 | 低 | 中 | 高 |
| Over-fetching | 常見 | 不會 | 不會 |
| 即時通訊 | 需 WebSocket | Subscription | Streaming |
| 適用場景 | 大多數 web API | 複雜前端、多資料來源 | 微服務間通訊 |
| 瀏覽器支援 | 完整 | 完整 | 需要 proxy |
| 快取 | HTTP cache 直接用 | 需要 client cache | 需自己做 |
實戰範例:一個完整的 API 回應
# Request
curl -X GET "https://api.example.com/v1/articles?page=2&per_page=10&sort=-published_at&category=tech" \
-H "Authorization: Bearer eyJ..." \
-H "Accept: application/json"
# Response (200 OK)
{
"data": [
{
"id": "art_789",
"title": "How to Design Good APIs",
"slug": "how-to-design-good-apis",
"category": "tech",
"author": {
"id": "usr_123",
"name": "Terry"
},
"publishedAt": "2026-03-15T08:00:00Z",
"readingTime": 8,
"tags": ["api", "design", "rest"]
}
],
"pagination": {
"page": 2,
"perPage": 10,
"totalPages": 15,
"totalCount": 142,
"hasNext": true,
"hasPrev": true
},
"meta": {
"requestId": "req_xyz789",
"timestamp": "2026-03-15T10:30:00Z"
}
}FAQ
Q1: REST 和 RESTful 有什麼不一樣?
嚴格來說,REST 是 Roy Fielding 定義的架構風格(包含 HATEOAS 等嚴格條件),RESTful 是「大致遵循 REST 原則」的 API。99% 的所謂 REST API 其實是 RESTful API。不用太糾結名詞,把基本原則做好比較重要。
Q2: 該用 camelCase 還是 snake_case?
兩個都有人用。重點是全站統一。JavaScript/TypeScript 生態圈偏好 camelCase,Python/Ruby 偏好 snake_case。選一個就別換了。
Q3: 到底要不要用 Response Envelope?
推薦用。雖然有人覺得多一層包裝是多餘的,但它讓你可以統一附加 meta 資訊(pagination、request ID),前端處理起來也更一致。
Q4: API 版本什麼時候該升?
只有 breaking change 才升版號。加新欄位不算 breaking change(forward compatible),移除欄位或改變型別才是。
Q5: GraphQL 是不是比 REST 好?
不是「好不好」的問題,是「適不適合」。如果你的前端需要靈活組合不同資料、有很多 nested 資料需求,GraphQL 很適合。如果你做的是簡單的 CRUD API,REST 就夠了,不需要為了潮而上 GraphQL。
系列導航
| # | 文章 | 狀態 |
|---|---|---|
| 01 | 好的前端專案該有什麼?一張體檢表 | |
| 02 | 好的後端框架需要具備哪些功能? | |
| 03 | 好的 API 該長什麼樣?設計原則與檢查清單 | 📍 你在這裡 |
| 04 | 好的資料庫設計需要什麼? | |
| 05 | 好的基礎建設需要什麼? | |
| 06 | CD Pipeline 需要什麼? | |
| 07 | 好的監控系統需要什麼? | |
| 08 | 好的開發者體驗(DX)需要什麼? |