RESTful API 設計原則

是什麼?

REST(Representational State Transfer)是一種 API 架構風格,以「資源」為中心,透過標準 HTTP 動詞對資源進行操作,並用狀態碼回傳結果。

ℹ️核心精神

REST 不是協議(Protocol),而是一組設計約束。遵守這些約束的 API 稱為 RESTful API。Roy Fielding 在 2000 年的博士論文中首次提出此概念。

核心觀念

常見誤區

⚠️常見誤區

  • 在 URL 中用動詞/getUsers/deleteUser/123 是錯的,正確做法是 GET /usersDELETE /users/123
  • 所有回應都回 200:把錯誤訊息塞在 body 裡但狀態碼永遠是 200,會讓客戶端無法正確處理錯誤
  • 混淆 PUT 和 PATCH:PUT 是完整替換資源,PATCH 是部分更新。用 PUT 但只傳部分欄位會導致未傳的欄位被清空

流程/步驟

1

辨識資源

找出系統中的核心名詞(users、orders、products)

2

設計 URL

用複數名詞組成階層路徑,如 /users/123/orders

3

對應 HTTP 動詞

GET 讀取、POST 新增、PUT/PATCH 更新、DELETE 刪除

4

定義狀態碼

成功用 2xx、客戶端錯誤用 4xx、伺服器錯誤用 5xx

5

設計回應格式

統一 JSON 結構,包含 data、error、pagination 等欄位

流程解讀:RESTful API 設計從辨識核心資源開始。每個資源對應一個 URL 路徑,透過 HTTP 動詞區分操作類型。狀態碼讓客戶端無需解析 body 就能判斷結果,最後統一回應格式確保 API 的一致性。

程式碼範例

C# 版本

csharp
// ASP.NET Core Controller — RESTful 風格
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    // GET /api/users
    [HttpGet]
    public async Task<ActionResult<IEnumerable<UserDto>>> GetAll()
    {
        var users = await _userService.GetAllAsync();
        return Ok(users); // 200
    }
 
    // GET /api/users/123
    [HttpGet("{id}")]
    public async Task<ActionResult<UserDto>> GetById(int id)
    {
        var user = await _userService.GetByIdAsync(id);
        if (user == null) return NotFound(); // 404
        return Ok(user); // 200
    }
 
    // POST /api/users
    [HttpPost]
    public async Task<ActionResult<UserDto>> Create(CreateUserDto dto)
    {
        var user = await _userService.CreateAsync(dto);
        return CreatedAtAction(nameof(GetById), new { id = user.Id }, user); // 201
    }
 
    // PUT /api/users/123
    [HttpPut("{id}")]
    public async Task<IActionResult> Update(int id, UpdateUserDto dto)
    {
        await _userService.UpdateAsync(id, dto);
        return NoContent(); // 204
    }
 
    // DELETE /api/users/123
    [HttpDelete("{id}")]
    public async Task<IActionResult> Delete(int id)
    {
        await _userService.DeleteAsync(id);
        return NoContent(); // 204
    }
}

TypeScript 版本

typescript
// Express.js — RESTful 路由
import express from "express";
const router = express.Router();
 
// GET /api/users
router.get("/users", async (req, res) => {
  const users = await userService.getAll();
  res.status(200).json({ data: users });
});
 
// GET /api/users/:id
router.get("/users/:id", async (req, res) => {
  const user = await userService.getById(req.params.id);
  if (!user) return res.status(404).json({ error: "User not found" });
  res.status(200).json({ data: user });
});
 
// POST /api/users
router.post("/users", async (req, res) => {
  const user = await userService.create(req.body);
  res.status(201).json({ data: user });
});
 
// DELETE /api/users/:id
router.delete("/users/:id", async (req, res) => {
  await userService.delete(req.params.id);
  res.status(204).send();
});

Python 版本

python
# FastAPI — RESTful 路由
from fastapi import FastAPI, HTTPException
 
app = FastAPI()
 
@app.get("/api/users")
async def get_users():
    users = await user_service.get_all()
    return {"data": users}  # 200
 
@app.get("/api/users/{user_id}")
async def get_user(user_id: int):
    user = await user_service.get_by_id(user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")
    return {"data": user}
 
@app.post("/api/users", status_code=201)
async def create_user(dto: CreateUserDto):
    user = await user_service.create(dto)
    return {"data": user}
 
@app.delete("/api/users/{user_id}", status_code=204)
async def delete_user(user_id: int):
    await user_service.delete(user_id)

架構圖/概念圖

Client
sends
HTTP Verb (GET/POST/PUT/DELETE)
operates on
Resource (/users/123)
returns
Status Code (200/201/404)
Response Body (JSON)

Client 發送帶有 HTTP Verb 的請求,指定要操作的 Resource。伺服器處理後回傳 Status Code 表示結果,並在 Response Body 中附帶資料。這種統一的互動模式讓任何客戶端都能用相同方式存取 API。

實戰補充

Q: URL 該用單數還是複數名詞?

A: 業界主流用複數名詞/users/orders)。即使取得單一資源(/users/123),路徑前綴仍用複數,保持一致性。GitHub、Stripe、Twilio 等知名 API 都採用此慣例。

Q: 巢狀資源該嵌到幾層?

A: 最多兩層(如 /users/123/orders)。超過兩層會讓 URL 過長且難以維護。如果需要更深層的關聯,改用查詢參數(/orders?userId=123)或獨立端點。

Q: 應該回傳什麼狀態碼?

A: 常用對照表 — GET 成功: 200、POST 成功: 201、PUT/PATCH/DELETE 成功: 204、參數錯誤: 400、未認證: 401、無權限: 403、找不到: 404、伺服器錯誤: 500。

理解測驗

🤔 以下哪個 URL 設計最符合 RESTful 規範?

🤔 用 PUT /api/users/123 更新使用者時,只傳了 name 欄位,其他欄位會怎樣?

🤔 POST 成功建立資源後,最適合回傳哪個狀態碼?

重點整理

💡一句話記住

REST = 用名詞定義資源、用動詞定義操作、用狀態碼定義結果。 口訣:「名詞當路徑,動詞選 HTTP」

| 概念 | 說明 | |------|------| | Resource | API 操作的對象,用 URL 表示 | | HTTP Verb | GET/POST/PUT/PATCH/DELETE 對應 CRUD | | Status Code | 2xx 成功、4xx 客戶端錯、5xx 伺服器錯 | | Stateless | 每次請求獨立,不依賴前次狀態 | | 命名規範 | 複數名詞、最多兩層巢狀、kebab-case |

你可能也想看

API 版本控制

按 ← → 鍵切換課程