RESTful API 設計全攻略:從資源命名到 HATEOAS,一次搞懂最佳實踐

更新日期: 2025 年 3 月 4 日

為什麼每個人都說要「RESTful」?

當你學習 Web 開發時,一定會不斷聽到「RESTful API」這個詞。

它聽起來像是某種神聖不可侵犯的規範,但其實它的核心只是一組讓 API 更好用、更直覺的設計原則

本文將揭開 RESTful API 的神秘面紗,從「資源命名」到「狀態碼選擇」,搭配大量實際案例,帶你掌握設計優雅 API 的關鍵心法。


資源導向設計:把一切當作「名詞」

在設計 API 時,一個核心概念是資源導向設計(Resource-Oriented Design,ROD)。

這種方法強調 API 的結構應該圍繞資源來構建,而不是基於動作(動詞)。

換句話說,API 應該回答「我們在操作什麼?」而不是「我們在做什麼?」

什麼是資源(Resource)?

在 RESTful API 設計中,資源是 API 操作的核心對象,任何可以被唯一標識、管理的事物都可以視為資源。

資源的範圍可以涵蓋具體的實體、抽象概念,甚至是某種集合。

常見的資源類型

  1. 實體物件(Entity):指的是具體存在的物品或用戶,例如:
    • 商品(Product)
    • 用戶(User)
  2. 抽象概念(Concept):指的是非具體的業務概念,例如:
    • 訂單(Order)
    • 權限(Permission)
  3. 集合(Collection):指的是某類資源的集合,例如:
    • 所有商品 /products
    • 熱門文章列表 /articles/popular

這些資源應該通過 API 提供 CRUD(Create、Read、Update、Delete)操作,並且遵循統一的設計規範來提升可讀性與可維護性。

設計資源 URI 的黃金法則

在 RESTful API 設計中,URI(統一資源識別符,Uniform Resource Identifier)應該清楚地表達資源的概念,而不是操作的動作。

以下是幾個關鍵原則:

🔹 1. 用名詞,不用動詞

URI 應該描述「是什麼」,而不是「做什麼」。

🚫 錯誤設計(帶有動詞):

GET  /getProducts
POST /createUser
PUT  /updateProduct/123

正確設計(只使用名詞):

GET  /products
POST /users
PUT  /products/123

🔹 2. 使用複數形式

統一使用複數名詞來表示資源集合,以保持一致性。例如:

✅ /products  (所有商品)  
✅ /users     (所有用戶)  

這樣能讓開發者直覺地理解這個 URI 代表的是一組資源,而不是單個資源。

🔹 3. 使用層次結構(階層式結構)

/ 來表達資源之間的從屬關係。

例如:

  • /products/123/reviews 代表商品 123 的所有評論
  • /users/456/orders 代表用戶 456 的所有訂單

這種層次結構讓 API 更加直觀,並且符合資源的自然關係。

🚫 錯誤設計

GET  /getReviewsByProductId?productId=123

正確設計

GET  /products/123/reviews

實際案例:電商平台 API 設計

以下是一個典型的電商平台 API 設計,示範如何用 RESTful 的方式來設計資源及其操作。

🔹 商品相關操作

GET    /products          # 取得所有商品  
POST   /products          # 新增商品  
GET    /products/123      # 取得商品 123  
PUT    /products/123      # 更新商品 123  
DELETE /products/123      # 刪除商品 123  

🔹 商品評論(從屬資源)

GET    /products/123/reviews  # 取得商品 123 的所有評論  
POST   /products/123/reviews  # 新增評論到商品 123  

這樣的設計方式符合層次結構,讓 API 易讀且具備可擴展性。


HTTP 方法語意化:動詞的標準化

RESTful API 的精髓之一是充分利用 HTTP 方法來表達操作意圖,而不是透過自訂動作來混淆語意。

例如,應該使用 GET 來獲取資源,而不是 POSTDELETE 來達成相同目的。

五大核心 HTTP 方法與對應語意

HTTP 方法操作意圖對應 SQL是否冪等(Idempotent)
GET取得資源SELECT✅ 是
POST新增資源INSERT❌ 否
PUT替換整個資源UPDATE✅ 是
PATCH部分更新資源UPDATE❌ 否
DELETE刪除資源DELETE✅ 是

🔹 冪等性解釋

冪等(Idempotency) 指的是重複執行相同操作時,結果應該保持一致。例如:

  • DELETE /products/123 不管執行多少次,結果都是資源被刪除(或已不存在),所以是冪等的。
  • POST /products 每次執行都會產生新的商品,因此不是冪等的。

常見錯誤與修正案例

🚫 錯誤範例:用 GET 來執行刪除操作

GET /deleteProduct?id=123   # ❌ 違反 RESTful 規範,GET 應該只用來讀取數據

正確做法:使用 DELETE 方法

DELETE /products/123   # ✅ 明確表達刪除意圖

這樣的做法不僅符合 HTTP 標準,也能讓 API 更加一致且具備可預測性,減少開發和維護成本。


狀態碼規範:用數字說故事的藝術

在 RESTful API 設計中,HTTP 狀態碼扮演著 API 與客戶端溝通的關鍵角色。

它就像 API 的「表情符號」,讓客戶端能夠透過數字直覺地理解請求的結果。

選擇適當的狀態碼不僅能提高 API 的可讀性,還能幫助開發者快速診斷錯誤並採取適當的處理措施。

必知的五大類 HTTP 狀態碼

HTTP 狀態碼由三位數字組成,第一個數字表示該狀態碼的類別。狀態碼可大致分為五大類,每一類的作用如下:

狀態碼範圍類別常見範例說明
1xx資訊性回應100 Continue客戶端可繼續請求(較少使用)
2xx成功200 OK, 201 Created表示請求成功
3xx重定向301 Moved Permanently, 302 Found需要進一步操作,例如重新導向
4xx客戶端錯誤400 Bad Request, 404 Not Found客戶端請求錯誤
5xx伺服器錯誤500 Internal Server Error伺服器發生錯誤

通常,在 API 設計中,2xx、4xx 和 5xx 類別的狀態碼是最常用的,因為它們分別表示成功、客戶端錯誤和伺服器錯誤的不同情境。

精選 HTTP 狀態碼使用情境

不同的 HTTP 狀態碼對應不同的 API 操作,以下是最常見的狀態碼及其適用場景:

🔹 成功類(2xx)

狀態碼名稱使用時機回應範例(JSON)
200 OK請求成功GET 成功取得資源{ "data": [...] }
201 Created資源已建立POST 成功新增資源{ "id": 123, "name": "New Product" }
204 No Content請求成功但無內容DELETE 操作成功,無需回傳內容無回應內容

200 OK 是最常見的狀態碼,適用於讀取GET)操作。

201 Created 則適用於成功新增資源POST),並且通常會在回應中返回新資源的 ID 或完整內容

204 No Content 則用於成功處理但無需回應內容的情境,例如 DELETE 操作。

🔹 客戶端錯誤類(4xx)

狀態碼名稱使用時機回應範例(JSON)
400 Bad Request請求格式錯誤傳入的參數格式錯誤、不合法{ "error": "Invalid email format" }
401 Unauthorized未授權缺少或提供了無效的身份驗證 Token{ "error": "Missing authentication token" }
403 Forbidden禁止訪問Token 有效,但權限不足{ "error": "You do not have permission to access this resource" }
404 Not Found資源不存在查詢的資源 ID 不存在{ "error": "Product 123 not found" }
429 Too Many Requests請求過多客戶端請求過於頻繁,觸發速率限制{ "error": "Rate limit exceeded" }

這些狀態碼通常用來幫助 API 告知客戶端發生了什麼錯誤

例如,400 代表請求格式錯誤,404 則表示資源不存在,而 401403 則涉及身份驗證和授權問題。

🔹 伺服器錯誤類(5xx)

狀態碼名稱使用時機回應範例(JSON)
500 Internal Server Error內部伺服器錯誤伺服器發生未預期錯誤{ "error": "An unexpected error occurred" }
503 Service Unavailable服務不可用伺服器過載或維護中{ "error": "Server is under maintenance" }

這些錯誤通常來自於伺服器端的問題,而非客戶端的請求錯誤。

新手陷阱:濫用 200 OK 狀態碼來包裹錯誤

🚫 錯誤示範:使用 200 OK 回應錯誤

{
  "status": "error",
  "message": "Product not found"
}

這種做法雖然讓所有回應都使用 200 OK,但卻違反了 RESTful API 的原則,因為客戶端無法透過狀態碼區分成功與失敗,必須額外解析 JSON 內容來判斷錯誤類型,這會增加開發和維護的複雜度。

正確做法:使用適當的 HTTP 狀態碼

HTTP/1.1 404 Not Found
Content-Type: application/json
{
  "error": "Product 123 not found"
}

這樣的做法有以下優勢:

  1. 更直觀:客戶端可以直接透過狀態碼判斷請求是否成功,而不需要解析 JSON 內容。
  2. 減少額外邏輯:前端或其他 API 使用者不需要額外撰寫判斷 status 欄位的邏輯,只需檢查 HTTP 狀態碼即可。
  3. 符合標準:這是 RESTful API 設計的最佳實踐。

這裡是更詳細且完整的版本,擴充了 HATEOAS 的概念、優勢與挑戰,並提供更具體的案例和應用情境。


HATEOAS:讓 API 會「自我介紹」

在 RESTful API 設計中,HATEOAS(Hypermedia As The Engine Of Application State)是一個強調 API 自描述性的重要概念。

傳統 API 設計通常要求客戶端事先知道所有 API 端點(Endpoint),但 HATEOAS 提供了一種更靈活的方法,使 API 具備「超連結導航」的能力。

讓客戶端能夠透過 API 回應內的連結,來動態發現可執行的操作

這種設計方式讓 API 更具可探索性,減少客戶端與伺服器的耦合,使系統更易於擴展和維護。


什麼是 HATEOAS?

HATEOAS 是 REST 架構中的一個重要特性,核心思想可以歸納為以下幾點:

  1. API 回應中提供相關操作的超連結:每個 API 回應不僅返回資源的數據,還包含與該資源相關的操作連結,例如查看詳細資訊、編輯、刪除等。
  2. 客戶端不需預先知道所有 API 端點:與傳統 API 不同,客戶端可以透過 API 回應內的 _links 欄位來動態發現可用的操作,而不必硬編碼(Hardcoded)所有 API URL。
  3. 類似網頁的「超連結導航」體驗:HATEOAS 提供的超連結就像網頁中的超連結,使用者可以點擊超連結來瀏覽不同的頁面,而客戶端則可以透過 _links 來發現並調用 API。

這樣的設計方式使得 API 更具適應性,即使 API 結構發生變化,客戶端仍然能夠透過 API 回應內的連結來動態適應,而不需要手動修改程式碼。

實際案例:帶有超連結的產品資料

假設我們有一個電商平台的 API,當客戶端請求獲取某個商品的詳細資訊時,HATEOAS 方式的 API 回應可能如下:

{
  "id": 123,
  "name": "無線耳機",
  "price": 2990,
  "_links": {
    "self": { "href": "/products/123" },
    "reviews": { "href": "/products/123/reviews" },
    "addToCart": { 
      "href": "/cart/items",
      "method": "POST",
      "body": { "productId": 123 }
    }
  }
}

🔹 解釋這段回應:

  1. self:表示當前資源的 API 端點,即 /products/123,客戶端可用來重新取得該商品資訊。
  2. reviews:提供該商品的評論 API,客戶端可以使用這個連結來獲取商品評論。
  3. addToCart:提供加入購物車的 API,並包含請求方法(POST)與請求參數(body),客戶端可以直接根據這個資訊發送請求,而無需事先知道 API 規則。

🔹 客戶端如何應用?

當前端應用收到這個 API 回應時,它可以動態讀取 _links,決定要顯示哪些按鈕與如何發送請求,而不需要手動寫死 API URL。

1️⃣ 顯示「查看評論」按鈕

如果 _links 中包含 reviews,表示這個商品有評論功能,前端就可以顯示「查看評論」按鈕,並動態取得評論 API 的 URL

<button id="reviewButton" style="display: none">查看評論</button>
// 取得商品資訊,並根據 _links 動態生成按鈕
fetch("/products/123")
  .then(response => response.json())
  .then(product => {
    if (product._links.reviews) {
      const reviewButton = document.getElementById("reviewButton");
      reviewButton.style.display = "block"; // 顯示按鈕
      reviewButton.onclick = () => fetchReviews(product._links.reviews.href);
    }
  });

function fetchReviews(url) {
  fetch(url)
    .then(response => response.json())
    .then(data => console.log("商品評論:", data));
}

這樣做的好處:

  • 不會寫死 /products/123/reviews URL,而是從 API 回應中獲取。
  • 如果 API 端點變更(例如變成 /items/123/comments),客戶端仍然能正常運作,無需修改代碼。
  • API 回應內沒有 reviews 時,不會顯示按鈕,確保 UI 只顯示可執行的操作。
2️⃣ 顯示「加入購物車」按鈕

如果 _links 中包含 addToCart,表示這個商品可以加入購物車,前端可以顯示「加入購物車」按鈕,並根據 API 回應發送請求:

<button id="cartButton" style="display: none">加入購物車</button>
// 取得商品資訊,並根據 _links 動態生成按鈕
fetch("/products/123")
  .then(response => response.json())
  .then(product => {
    if (product._links.addToCart) {
      const cartButton = document.getElementById("cartButton");
      cartButton.style.display = "block"; // 顯示按鈕
      cartButton.onclick = () => addToCart(product._links.addToCart);
    }
  });

function addToCart(cartLink) {
  fetch(cartLink.href, {
    method: cartLink.method,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(cartLink.body)
  })
    .then(response => response.json())
    .then(data => console.log("加入購物車結果:", data));
}

這樣做的好處:

  • POST 目標 URL 並未寫死,而是從 API 回應的 _links 內動態獲取。
  • POST 所需的參數(body)也來自 API 回應,確保前端無需手動定義請求格式,減少錯誤。
  • 如果未來購物車 API 變更(例如 /cart/items 變成 /shopping-cart/add),客戶端依然能正常運作,無需修改代碼。

為什麼這樣做更靈活?

避免硬編碼 API URL

  • 如果 API 變更,客戶端無需修改代碼,只需依據 API 回應的 _links 發送請求。

根據 API 回應來決定顯示哪些功能

  • API 未提供 reviews 時,前端不會顯示「查看評論」按鈕,避免無效操作。

提升 API 的可探索性

  • _links 讓開發者能夠透過 API 回應自動發現可用操作,而不需要查閱 API 文件。

HATEOAS 的優缺點

雖然 HATEOAS 為 API 設計帶來了許多優勢,但也有一些挑戰需要克服。

以下是 HATEOAS 的優勢與可能的挑戰:

優勢挑戰
降低客戶端與伺服器的耦合度:API 回應提供所有操作的超連結,客戶端不需手動設定 API URL,當 API 更新時,客戶端仍能適應變更。增加回應內容大小:因為 API 回應中需要包含額外的 _links 資訊,可能會使請求的回應大小增加,影響傳輸效率。
動態適應 API 變化:當 API 端點變更時,只要回應的 _links 保持更新,客戶端仍然可以正常運行,無需修改代碼。需設計統一的連結格式標準:不同 API 可能會有不同的 _links 格式,團隊需要制定一致的標準來確保 API 的可讀性與一致性。
提升 API 可探索性:HATEOAS 讓 API 變得更直觀,開發者可以透過 API 回應內的超連結來理解可用的操作,而不需要查閱 API 文件。對新手來說概念較抽象:與傳統 API 相比,HATEOAS 需要更深的理解,開發者需要習慣透過 API 回應中的連結來操作資源,而不是直接調用固定的 API URL。

HATEOAS 適用場景與實務應用

HATEOAS 在以下幾種情境中特別有用:

  1. API 變動頻繁的系統
    • 如果 API 經常新增或修改端點,HATEOAS 能讓客戶端適應變更,而不需要頻繁更新程式碼。
  2. 複雜的業務流程
    • 例如電商平台中的訂單狀態變更,當訂單處於「待付款」、「已付款」、「已出貨」等不同狀態時,HATEOAS 可動態提供相應的操作(例如「付款」、「取消訂單」、「查看物流」等)。
  3. 需要提升 API 可探索性的場景
    • 當開發團隊希望 API 更加自描述,使開發者能夠透過 API 回應了解可用操作時,HATEOAS 是一個很好的選擇。

🔹 但 HATEOAS 不適用於:

  • 簡單的 API:如果 API 端點固定且變動不大,HATEOAS 可能會增加不必要的複雜性。
  • 對效能要求極高的場景:由於 HATEOAS 會增加回應的大小,可能會影響 API 的性能,因此在高併發場景下需要謹慎考量。

總結:RESTful 不是教條,而是溝通默契

RESTful API 的四大原則,最終目標是建立開發者之間的共同語言

  1. 資源導向設計 → 讓 URI 直觀如文件路徑
  2. HTTP 方法語意化 → 像使用資料庫一樣操作資源
  3. 狀態碼規範 → 用數字快速傳遞結果
  4. HATEOAS → 讓 API 自己告訴你能做什麼

這些原則並非鐵律,實際開發中可根據需求調整。

關鍵在於保持一致性,讓團隊成員與第三方開發者都能輕鬆理解你的設計。

新手常見問答

一定要用複數名詞嗎?例如 /product 可以嗎?

複數是為了統一性(如 /products/123/product/123 更直覺),但非強制。重點是團隊保持一致。

PUT 和 PATCH 到底差在哪?

PUT 替換整個資源(需傳遞完整欄位),PATCH 只更新部分欄位(傳遞差異即可)。

HATEOAS 實務上真的有用嗎?

在大型系統或需要高度可探索性的 API(如公共服務)中價值明顯,但小型專案可先省略。

Similar Posts