cover

你的 API 是契約,不是隨便寫寫的 endpoint

一句話總結:API 是前後端之間的合約,設計好了雙方各自開發互不干擾,設計爛了就是無止盡的聯調地獄。

結論先講:好的 API 看到 URL 就能猜出行為、看到錯誤就知道怎麼處理、看到文件就能直接串接。做不到這三點,你的 API 就只是一堆散亂的 endpoint。

你有沒有串過這種 API?

取得使用者列表是 GET /getUsers,建立訂單是 POST /create-order,查詢商品是 GET /fetch_products。三個 endpoint 三種命名風格,錯誤回應一個回 { message: "..." }、一個回 { error: "..." }、一個直接回字串。

恭喜,你剛走進了聯調地獄的大門。

API 設計混亂的代價不是「工程師覺得醜」,是前端每串一個 endpoint 都要猜格式、後端每改一次都要擔心前端會不會壞、新人接手的時候一臉問號。

RESTful 的核心:把一切當資源

REST 的思想很簡單——URL 代表資源的位置,HTTP 方法代表對資源的操作。

GET    /users       → 取得使用者列表
GET    /users/42    → 取得 ID 42 的使用者
POST   /users       → 建立新使用者
PATCH  /users/42    → 部分更新使用者
DELETE /users/42    → 刪除使用者
GET    /users/42/orders → 取得該使用者的訂單

看到了嗎?URL 裡沒有動詞。不是 /getUsers/createUser/deleteUser。動作由 HTTP method 來表達,URL 只負責指出「操作的對象是誰」。

幾個命名原則:

  • 用名詞不用動詞/users 不是 /getUsers
  • 用複數/users 不是 /user
  • 用 kebab-case/user-profiles 不是 /userProfiles
  • 巢狀不超過兩層/users/42/orders 可以,/users/42/orders/7/items/3/reviews 就太深了

HTTP 方法不是裝飾品

每個 HTTP 方法都有明確的語義,不是你高興用哪個就用哪個:

  • GET:讀取,冪等,安全——不管你 GET 幾次,結果都一樣,不會改變任何東西
  • POST:建立,不冪等——連續 POST 兩次可能建立兩筆資料
  • PUT:完整取代——你得送完整的物件過去
  • PATCH:部分更新——只送你要改的欄位
  • DELETE:刪除,冪等——刪一次跟刪三次結果一樣

冪等性(Idempotency)不是學術名詞,它直接影響前端要不要做 retry 機制。GET、PUT、DELETE 可以安全重試,POST 不行。這不是你可以「看情況」的事。

狀態碼:跟前端溝通的語言

你有沒有遇過所有 API 都回 200,錯誤也回 200 只是 body 裡有個 success: false?那你的前端工程師一定恨你。

正確使用狀態碼:

成功系列:

  • 200 OK:GET 成功、PATCH 成功
  • 201 Created:POST 建立新資源成功
  • 204 No Content:DELETE 成功(沒有 body 要回)

客戶端錯誤系列:

  • 400 Bad Request:你送的格式有問題
  • 401 Unauthorized:你沒登入或 token 過期
  • 403 Forbidden:你登入了但沒權限
  • 404 Not Found:你找的東西不存在
  • 409 Conflict:重複建立
  • 422 Unprocessable Entity:格式對但業務邏輯不允許
  • 429 Too Many Requests:你打太快了

伺服器錯誤系列:

  • 500:後端炸了
  • 502:上游服務掛了
  • 503:服務暫時不可用

401 跟 403 的差異很多人搞混:401 是「你是誰?」(沒認證),403 是「我知道你是誰,但你不行」(沒權限)。

分頁:不要一次吐幾萬筆

當資源量大的時候,分頁是必須的。兩種常見做法:

Offset-based(傳統):

GET /users?page=2&limit=20

好處是可以跳頁,壞處是資料即時異動時可能重複或漏掉。

Cursor-based(推薦用於即時資料流):

GET /users?cursor=eyJpZCI6MTAwfQ&limit=20

效能更好、不怕資料異動,但沒辦法跳到任意頁。

排序跟過濾用 query parameter:

GET /users?status=active&role=admin&sort=-created_at,name

- 代表降序,逗號分隔多重排序。

版本管理:從第一天就開始

API 一定會改,問題是你改了之後舊的 client 怎麼辦。

最推薦也最直觀的做法是 URL 路徑版本:/v1/users。為什麼?因為 API Gateway 層做路由分流最方便,debug 的時候看 URL 就知道是哪個版本,CDN 快取也不會有問題。

Header 版本 Accept: application/vnd.api+json;version=1 看起來很優雅,但實務上不容易 debug、CDN 快取困難,大部分團隊用不到。

統一錯誤格式:前端只需要一個 interceptor

所有錯誤回應長一樣,前端就可以用一個 interceptor 處理所有錯誤。格式建議:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "輸入資料驗證失敗",
    "details": [
      { "field": "email", "message": "email 格式不正確" },
      { "field": "password", "message": "密碼長度至少 8 個字元" }
    ],
    "requestId": "req_abc123"
  }
}

requestId 很重要——前端回報問題時,後端可以用這個 ID 直接從日誌裡撈到對應的 request,不用在茫茫 log 海裡大海撈針。

這篇的重點回顧

API 設計的基礎是一致性:命名統一、方法語義正確、狀態碼不亂用、錯誤格式一致。下一篇我們聊認證機制——JWT、OAuth、API Key 的選用時機跟安全注意事項。

系列文章:

延伸閱讀:

「一致的 API 設計就像交通規則——遵守的時候感覺多此一舉,不遵守的時候大家一起車禍。」