alltools.one
Development
2024-01-01
12 min
Development Team
json-apirest-apiapi-designweb-developmentbackend

JSON API 最佳實務:RESTful API 設計完整指南

建構穩健的 JSON API 需要的不僅是將資料以 JSON 格式返回。本全面指南涵蓋了設計、實作和維護安全、高效能且開發人員友善的 API 的基本最佳實務。

API 卓越:遵循這些最佳實務可以將 API 採用率提高 300%,並將整合時間減少 70%。設計良好的 API 成為成功數位產品的基礎。

為何 JSON API 最佳實務重要

良好 API 設計的影響

設計良好的 API 提供:

  • 更快的整合 給開發人員
  • 減少支援 請求和文件需求
  • 更高的採用率 和開發人員滿意度
  • 更容易的維護 和隨時間演進
  • 更好的效能 和可擴展性

常見 API 問題

不良 API 設計導致:

  • 不一致的回應 讓開發人員困惑
  • 來自不當驗證的安全漏洞
  • 來自低效資料傳輸的效能問題
  • 由於不清楚的文件導致的整合失敗
  • 來自技術債務的維護噩夢

RESTful API 基礎

REST 原則

Representational State Transfer (REST) 核心原則:

  1. Client-Server Architecture:明確的關注點分離
  2. Stateless:每個請求包含所有必要資訊
  3. Cacheable:回應在適當時應可快取
  4. Uniform Interface:一致的資源識別和操作
  5. Layered System:架構可由階層式層組成

HTTP 方法及其正確使用

標準 HTTP 方法:

GET /api/users          # Retrieve all users
GET /api/users/123      # Retrieve specific user
POST /api/users         # Create new user
PUT /api/users/123      # Update entire user resource
PATCH /api/users/123    # Partial update of user
DELETE /api/users/123   # Delete user

方法指南:

  • GET:安全且冪等,無副作用
  • POST:非冪等,創建資源
  • PUT:冪等,取代整個資源
  • PATCH:不一定冪等,部分更新
  • DELETE:冪等,移除資源

URL 結構和命名慣例

基於資源的 URL

良好的 URL 設計:

✅ GET /api/v1/users
✅ GET /api/v1/users/123
✅ GET /api/v1/users/123/orders
✅ POST /api/v1/orders

避免這些模式:

❌ GET /api/getUsers
❌ POST /api/createUser
❌ GET /api/user_orders?userId=123

命名慣例

資源命名規則:

  • 使用 複數名詞 來表示集合(/users,而非 /user
  • 使用 小寫 並以連字號提高可讀性(/user-profiles
  • 在整個 API 中保持 一致性
  • 使用 巢狀資源 來表示關係(/users/123/orders

查詢參數:

GET /api/users?page=2&limit=50&sort=created_at&order=desc
GET /api/products?category=electronics&min_price=100
GET /api/posts?search=json&tags=api,development

JSON 回應結構

一致的回應格式

標準成功回應:

{
  "success": true,
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com",
    "created_at": "2024-01-01T10:00:00Z"
  },
  "message": "User retrieved successfully"
}

集合回應:

{
  "success": true,
  "data": [
    {
      "id": 123,
      "name": "John Doe",
      "email": "john@example.com"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 1250,
    "pages": 25,
    "has_next": true,
    "has_prev": false
  },
  "message": "Users retrieved successfully"
}

資料格式化標準

日期和時間:

{
  "created_at": "2024-01-01T10:00:00Z",
  "updated_at": "2024-01-01T15:30:00Z"
}

貨幣值:

{
  "price": {
    "amount": 1999,
    "currency": "USD",
    "formatted": "$19.99"
  }
}

布林值:

{
  "is_active": true,
  "email_verified": false,
  "has_premium": null
}

錯誤處理最佳實務

HTTP 狀態碼

成功碼:

  • 200 OK:成功的 GET、PUT、PATCH
  • 201 Created:成功的 POST
  • 204 No Content:成功的 DELETE

客戶端錯誤碼:

  • 400 Bad Request:無效的請求資料
  • 401 Unauthorized:需要驗證
  • 403 Forbidden:存取被拒
  • 404 Not Found:資源不存在
  • 422 Unprocessable Entity:驗證錯誤

伺服器錯誤碼:

  • 500 Internal Server Error:通用伺服器錯誤
  • 502 Bad Gateway:上游伺服器錯誤
  • 503 Service Unavailable:暫時無法使用

錯誤回應格式

標準錯誤回應:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The request data is invalid",
    "details": [
      {
        "field": "email",
        "message": "Email format is invalid",
        "rejected_value": "invalid-email"
      },
      {
        "field": "age",
        "message": "Age must be between 18 and 120",
        "rejected_value": 15
      }
    ]
  },
  "timestamp": "2024-01-01T10:00:00Z",
  "request_id": "req_123456789"
}

安全警告:切勿在錯誤訊息中暴露敏感資訊。提供足夠的除錯細節,但不透露系統內部或使用者資料。

驗證和授權

驗證方法

API 金鑰驗證:

GET /api/users
Authorization: Bearer api_key_here

JWT 權杖驗證:

GET /api/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

OAuth 2.0:

GET /api/users
Authorization: Bearer oauth_access_token_here

安全最佳實務

基本安全措施:

  • 僅 HTTPS:切勿透過 HTTP 傳輸敏感資料
  • 速率限制:防止濫用和 DoS 攻擊
  • 輸入驗證:清理和驗證所有輸入
  • 輸出編碼:防止 XSS 攻擊
  • CORS 配置:正確配置跨來源請求

速率限制標頭:

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200

分頁策略

基於偏移的分頁

請求:

GET /api/users?page=2&limit=50

回應:

{
  "data": [...],
  "pagination": {
    "page": 2,
    "limit": 50,
    "total": 1250,
    "pages": 25,
    "offset": 50
  }
}

基於游標的分頁

請求:

GET /api/users?cursor=eyJpZCI6MTIzfQ&limit=50

回應:

{
  "data": [...],
  "pagination": {
    "limit": 50,
    "has_next": true,
    "next_cursor": "eyJpZCI6MTczfQ",
    "prev_cursor": "eyJpZCI6NzN9"
  }
}

連結標頭分頁

回應標頭:

Link: <https://api.example.com/users?page=1>; rel="first",
      <https://api.example.com/users?page=2>; rel="prev",
      <https://api.example.com/users?page=4>; rel="next",
      <https://api.example.com/users?page=25>; rel="last"

篩選、排序和搜尋

查詢參數慣例

篩選:

GET /api/products?category=electronics&status=active&min_price=100
GET /api/users?role=admin&created_after=2024-01-01

排序:

GET /api/users?sort=created_at&order=desc
GET /api/products?sort=price,name&order=asc,desc

搜尋:

GET /api/users?search=john&fields=name,email
GET /api/posts?q=json%20api&in=title,content

進階篩選

運算子:

GET /api/products?price[gte]=100&price[lte]=500
GET /api/users?created_at[between]=2024-01-01,2024-12-31
GET /api/posts?tags[in]=api,json,rest

版本控制策略

URL 路徑版本控制

GET /api/v1/users
GET /api/v2/users

標頭版本控制

GET /api/users
Accept: application/vnd.api+json;version=1
API-Version: 2

查詢參數版本控制

GET /api/users?version=1

版本控制最佳實務:

  • Semantic Versioning:使用 major.minor.patch 格式
  • 向後相容性:在合理期間維護舊版本
  • 棄用通知:提供清晰的遷移路徑
  • 文件:維護版本特定文件

效能最佳化

回應最佳化

欄位選擇:

GET /api/users?fields=id,name,email
GET /api/posts?include=author,comments&fields[posts]=title,body&fields[author]=name

壓縮:

Accept-Encoding: gzip, deflate
Content-Encoding: gzip

快取策略

HTTP 快取標頭:

Cache-Control: public, max-age=3600
ETag: "abc123def456"
Last-Modified: Wed, 01 Jan 2024 10:00:00 GMT

條件式請求:

GET /api/users/123
If-None-Match: "abc123def456"
If-Modified-Since: Wed, 01 Jan 2024 10:00:00 GMT

資料庫最佳化

N+1 查詢預防:

{
  "data": [
    {
      "id": 1,
      "title": "Post Title",
      "author": {
        "id": 123,
        "name": "John Doe"
      },
      "comments": [
        {
          "id": 456,
          "content": "Great post!",
          "author": {
            "id": 789,
            "name": "Jane Smith"
          }
        }
      ]
    }
  ]
}

內容協商

Accept 標頭

JSON 回應:

Accept: application/json
Content-Type: application/json

XML 回應:

Accept: application/xml
Content-Type: application/xml

多格式支援

API 回應:

GET /api/users/123
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 123,
  "name": "John Doe"
}

文件最佳實務

OpenAPI/Swagger 規格

基本 API 定義:

openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
  description: A simple user management API

paths:
  /users:
    get:
      summary: Get all users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'

互動式文件

基本文件元素:

  • 所有端點的清晰描述
  • 每個操作的請求/回應範例
  • 錯誤情境 及其回應
  • 驗證需求 和範例
  • 速率限制 資訊
  • 多語言的 SDK 和程式碼範例

測試策略

API 測試金字塔

單元測試:

describe('User API', () => {
  test('should create user with valid data', async () => {
    const userData = {
      name: 'John Doe',
      email: 'john@example.com'
    };
    
    const response = await request(app)
      .post('/api/users')
      .send(userData)
      .expect(201);
      
    expect(response.body.data.name).toBe('John Doe');
  });
});

整合測試:

describe('User Integration Tests', () => {
  test('should handle user creation workflow', async () => {
    // Create user
    const createResponse = await createUser(userData);
    expect(createResponse.status).toBe(201);
    
    // Verify user exists
    const getResponse = await getUser(createResponse.body.data.id);
    expect(getResponse.status).toBe(200);
    
    // Update user
    const updateResponse = await updateUser(user.id, updatedData);
    expect(updateResponse.status).toBe(200);
  });
});

合約測試

API 合約範例:

{
  "consumer": "Frontend App",
  "provider": "User API",
  "interactions": [
    {
      "description": "Get user by ID",
      "request": {
        "method": "GET",
        "path": "/api/users/123"
      },
      "response": {
        "status": 200,
        "headers": {
          "Content-Type": "application/json"
        },
        "body": {
          "id": 123,
          "name": "John Doe",
          "email": "john@example.com"
        }
      }
    }
  ]
}

監控和分析

基本指標

效能指標:

  • 回應時間百分位數 (p50, p95, p99)
  • 請求速率和吞吐量
  • 按端點和狀態碼的錯誤率
  • 快取命中/未命中比率

業務指標:

  • API 採用和使用模式
  • 最受歡迎的端點
  • 開發人員入門成功率
  • 支援票券類別

記錄最佳實務

結構化記錄:

{
  "timestamp": "2024-01-01T10:00:00Z",
  "level": "INFO",
  "method": "GET",
  "path": "/api/users/123",
  "status_code": 200,
  "response_time": 150,
  "user_id": "user_456",
  "request_id": "req_789",
  "ip_address": "192.168.1.1"
}

進階主題

Webhooks 實作

Webhook 負載:

{
  "event": "user.created",
  "timestamp": "2024-01-01T10:00:00Z",
  "data": {
    "id": 123,
    "name": "John Doe",
    "email": "john@example.com"
  },
  "webhook_id": "wh_abc123"
}

GraphQL 與 REST

何時選擇 GraphQL:

  • 複雜的資料關係
  • 多種客戶端類型具有不同需求
  • 需要即時訂閱
  • 強類型需求

何時堅持 REST:

  • 簡單的 CRUD 操作
  • 快取至關重要
  • 團隊熟悉 REST
  • 檔案上傳/下載密集

API 演進和維護

棄用策略

棄用流程:

  1. 宣布棄用 並提供清晰的時間表
  2. 提供遷移指南 和範例
  3. 監控棄用端點的使用
  4. 在過渡期間提供支援
  5. 寬限期後移除棄用功能

破壞性與非破壞性變更

非破壞性變更:

  • 新增新的選用欄位
  • 新增新的端點
  • 新增新的選用查詢參數
  • 將必要欄位設為選用

破壞性變更:

  • 移除欄位或端點
  • 變更欄位類型或格式
  • 將選用欄位設為必要
  • 變更驗證需求

結論

建構優秀的 JSON API 需要注意細節、一致性和對技術及使用者體驗考量的深入理解。最佳的 API 對開發人員來說感覺直觀,並提供清晰、可預測的介面,以實現快速整合和開發。

請記住,API 設計是關於在您的服務及其消費者之間建立合約。盡可能使該合約清晰、穩定且開發人員友善,您的 API 將成為推動採用和業務成功的寶貴資產。

API 成功的關鍵是將您的 API 視為產品,具有真實使用者及其真實需求。以同理心設計、徹底文件化,並根據回饋迭代。

準備建構更好的 API 嗎?使用我們的 JSON Formatter 來確保您的 API 回應正確格式化和驗證。

Published on 2024-01-01 by Development Team