
前言:API 是前後端之間的契約
流程概覽
flowchart LR A[資源建模<br/>Resource Modeling] --> B[URL 設計<br/>RESTful 命名] B --> C[Request / Response<br/>格式定義] C --> D[錯誤處理<br/>統一格式] D --> E[認證機制<br/>JWT / OAuth] E --> F[API 文件化<br/>OpenAPI] style A fill:#4CAF50,color:#fff style B fill:#2196F3,color:#fff style C fill:#FF9800,color:#fff style D fill:#F44336,color:#fff style E fill:#9C27B0,color:#fff style F fill:#009688,color:#fff
在現代軟體架構中,API(Application Programming Interface)扮演著前端與後端之間的契約角色。一份設計良好的 API,就像一份清晰的合約——雙方都能依據它各自獨立開發、測試與部署,而不需要時刻溝通實作細節。
反之,一份設計混亂的 API 會導致前端工程師不斷猜測回傳格式、後端工程師頻繁處理相容性問題,最終整個團隊陷入無止盡的聯調地獄。
好的 API 設計應該具備以下特質:
- 一致性(Consistency):命名風格、回傳格式、錯誤處理全部統一
- 可預測性(Predictability):看到 URL 就能推測行為
- 安全性(Security):適當的認證與授權機制
- 可文件化(Documentability):能自動產生清楚的文件
本文將從 RESTful 設計原則、認證機制到 API 文件化三大面向,完整介紹 API 設計的最佳實踐。
API 請求生命週期
一個典型的 API 請求會經過多層處理。以下是從客戶端發出請求到取得資料庫回應的完整流程:
sequenceDiagram participant C as Client (Browser/App) participant A as Auth Layer participant G as API Gateway participant CT as Controller participant S as Service Layer participant D as Database C->>A: 1. 發送請求 + Token A->>A: 2. 驗證 Token / API Key alt 認證失敗 A-->>C: 401 Unauthorized end A->>G: 3. 轉發已驗證的請求 G->>G: 4. Rate Limiting / 路由 alt 超過速率限制 G-->>C: 429 Too Many Requests end G->>CT: 5. 路由到對應 Controller CT->>CT: 6. 參數驗證 / 反序列化 alt 參數無效 CT-->>C: 400 Bad Request end CT->>S: 7. 呼叫 Service 處理業務邏輯 S->>D: 8. 查詢 / 寫入資料庫 D-->>S: 9. 回傳資料 S-->>CT: 10. 封裝業務結果 CT-->>G: 11. 序列化回應 G-->>C: 12. 回傳 HTTP Response
每一層都有其職責:Auth Layer 負責身份驗證、API Gateway 負責流量控制與路由、Controller 負責參數校驗、Service 負責業務邏輯、Database 負責資料持久化。這種分層架構讓每一層都可以獨立測試與替換。
核心概念一:RESTful 設計原則
URL 命名慣例
RESTful API 的核心思想是將一切視為「資源(Resource)」,URL 代表資源的位置,HTTP 方法代表對資源的操作。
| 模式 | 說明 | 範例 |
|---|---|---|
/resources | 資源集合 | GET /users 取得所有使用者 |
/resources/:id | 單一資源 | GET /users/42 取得 ID 為 42 的使用者 |
/resources/:id/sub-resources | 巢狀資源 | GET /users/42/orders 取得該使用者的訂單 |
/resources/:id/sub-resources/:subId | 巢狀單一資源 | GET /users/42/orders/7 取得特定訂單 |
命名規範:
- 使用名詞而非動詞:
/users而非/getUsers - 使用複數形式:
/users而非/user - 使用 kebab-case:
/user-profiles而非/userProfiles - 避免過深的巢狀:最多兩層,超過的用查詢參數或獨立端點處理
HTTP 方法語義
| 方法 | 語義 | 冪等性 | 安全性 | 典型用途 |
|---|---|---|---|---|
GET | 讀取資源 | 是 | 是 | 查詢列表或單筆資料 |
POST | 建立資源 | 否 | 否 | 新增使用者、提交訂單 |
PUT | 完整取代資源 | 是 | 否 | 更新整個使用者資料 |
PATCH | 部分更新資源 | 是 | 否 | 只更新使用者的 email |
DELETE | 刪除資源 | 是 | 否 | 刪除一筆訂單 |
冪等性(Idempotency) 意味著同一個請求執行一次或多次,結果都一樣。
GET、PUT、DELETE都是冪等的,但POST不是——連續兩次POST可能會建立兩筆資料。
HTTP 狀態碼
狀態碼是 API 與客戶端溝通的重要語言,必須正確使用:
2xx 成功回應:
| 狀態碼 | 含義 | 使用時機 |
|---|---|---|
200 OK | 請求成功 | GET 成功取得資料、PUT/PATCH 更新成功 |
201 Created | 資源已建立 | POST 成功建立新資源 |
204 No Content | 成功但無內容 | DELETE 成功刪除資源 |
4xx 客戶端錯誤:
| 狀態碼 | 含義 | 使用時機 |
|---|---|---|
400 Bad Request | 請求格式錯誤 | 缺少必填欄位、JSON 格式不正確 |
401 Unauthorized | 未認證 | 沒有提供 Token 或 Token 過期 |
403 Forbidden | 無權限 | Token 有效但權限不足 |
404 Not Found | 資源不存在 | 查詢的 ID 不存在 |
409 Conflict | 資源衝突 | 重複建立已存在的資源 |
422 Unprocessable Entity | 語義錯誤 | 格式正確但業務邏輯不允許 |
429 Too Many Requests | 請求過多 | 超過速率限制 |
5xx 伺服器錯誤:
| 狀態碼 | 含義 | 使用時機 |
|---|---|---|
500 Internal Server Error | 伺服器內部錯誤 | 未預期的程式例外 |
502 Bad Gateway | 上游伺服器錯誤 | 後端服務無回應 |
503 Service Unavailable | 服務暫時不可用 | 維護中或過載 |
分頁、過濾與排序
當資源集合可能包含大量資料時,必須實作分頁機制:
Offset-based 分頁(傳統方式):
GET /users?page=2&limit=20
- 優點:簡單直觀、可跳頁
- 缺點:資料異動時可能產生重複或遺漏
Cursor-based 分頁(推薦用於即時資料流):
GET /users?cursor=eyJpZCI6MTAwfQ&limit=20
- 優點:效能更好、不受資料異動影響
- 缺點:無法跳到任意頁
過濾與排序:
GET /users?status=active&role=admin&sort=-created_at,name
- 過濾:使用查詢參數
?status=active - 排序:
sort=field升序,sort=-field降序 - 多重排序:逗號分隔
sort=-created_at,name
API 版本管理策略
API 演進是不可避免的,版本管理策略決定了如何處理向後相容性:
| 策略 | 範例 | 優點 | 缺點 |
|---|---|---|---|
| URL 路徑 | /v1/users | 直觀、容易路由 | URL 變得冗長 |
| Header | Accept: application/vnd.api+json;version=1 | URL 乾淨 | 不易除錯、CDN 快取困難 |
| Query 參數 | /users?version=1 | 彈性高 | 容易忘記傳遞 |
實務建議: URL 路徑版本 (
/v1/) 是最常見且最推薦的做法,因為它最直觀、最容易在 API Gateway 層做路由分流。
統一錯誤回應格式
所有 API 錯誤都應該回傳一致的格式,讓前端能用統一的方式處理:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "輸入資料驗證失敗",
"details": [
{
"field": "email",
"message": "email 格式不正確"
},
{
"field": "password",
"message": "密碼長度至少 8 個字元"
}
],
"requestId": "req_abc123",
"timestamp": "2024-09-15T10:30:00Z"
}
}requestId 對於除錯非常重要——前端回報問題時,後端可以透過這個 ID 快速追蹤到對應的日誌。
HATEOAS
HATEOAS(Hypermedia as the Engine of Application State)是 REST 的最高成熟度等級,回應中包含相關操作的連結:
{
"id": 42,
"name": "Alice",
"links": {
"self": "/users/42",
"orders": "/users/42/orders",
"update": "/users/42",
"delete": "/users/42"
}
}理論上 HATEOAS 讓 API 更具自描述性,但在實務中,大多數前端應用不會動態解析這些連結——它們通常在編譯時就已經確定了 API 路徑。因此 HATEOAS 在多數專案中屬於「知道就好」的層級,除非你在設計一個需要高度動態探索的公開 API。
核心概念二:認證機制
Session-based vs Token-based
| 比較面向 | Session-based | Token-based |
|---|---|---|
| 狀態 | 有狀態(伺服器保存 Session) | 無狀態(客戶端持有 Token) |
| 儲存位置 | 伺服器記憶體 / Redis | 客戶端(Cookie / Header) |
| 擴展性 | 需要共享 Session Store | 天然支援水平擴展 |
| 跨域 | 需處理 CORS + Cookie | Bearer Token 較容易跨域 |
| 適用場景 | 傳統 Server-rendered 應用 | SPA、行動應用、微服務 |
現代架構中,Token-based 認證已成為主流,尤其是 JWT(JSON Web Token)。
JWT(JSON Web Token)
JWT 由三段以 . 分隔的 Base64 編碼字串組成:
header.payload.signature
- Header:演算法與 Token 類型,例如
{"alg": "HS256", "typ": "JWT"} - Payload:承載的資料(Claims),例如
{"sub": "user_42", "role": "admin", "exp": 1694780000} - Signature:使用密鑰對 Header + Payload 簽名,確保內容未被竄改
Access Token vs Refresh Token:
| 類型 | 用途 | 有效期 | 儲存位置 |
|---|---|---|---|
| Access Token | 存取 API 資源 | 短(15 分鐘 ~ 1 小時) | 記憶體或 HttpOnly Cookie |
| Refresh Token | 換發新的 Access Token | 長(7 天 ~ 30 天) | HttpOnly Secure Cookie |
短效的 Access Token 降低了 Token 洩漏的風險——即使被盜也只有短暫的有效時間。Refresh Token 則讓使用者不需要頻繁重新登入。
OAuth 2.0
OAuth 2.0 是一個授權框架,常見兩種流程:
Authorization Code Flow(適用於有後端的 Web 應用):
- 使用者點擊「透過 Google 登入」
- 瀏覽器跳轉到 Google 授權頁面
- 使用者同意授權
- Google 回傳 Authorization Code 到後端
- 後端用 Code 向 Google 換取 Access Token
- 後端用 Access Token 取得使用者資訊
Client Credentials Flow(適用於服務對服務通訊):
- 服務 A 使用 Client ID + Client Secret 向授權伺服器請求 Token
- 授權伺服器回傳 Access Token
- 服務 A 用 Token 存取服務 B 的 API
API Key
API Key 是最簡單的認證方式,適用於服務對服務的通訊:
GET /api/data HTTP/1.1
X-API-Key: sk_live_abc123def456
- 優點:實作簡單
- 缺點:無法攜帶使用者身份資訊、洩漏風險較高
- 適用場景:第三方整合、內部微服務、公開但需計量的 API
速率限制(Rate Limiting)
速率限制是防止 API 被濫用的關鍵機制,常見演算法:
- Fixed Window:固定時間視窗(例如每分鐘 100 次)
- Sliding Window:滑動時間視窗,更平滑
- Token Bucket:令牌桶演算法,允許突發流量
回應 Header 應包含速率限制資訊:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 67
X-RateLimit-Reset: 1694780060
核心概念三:API 文件化
OpenAPI / Swagger 規範
OpenAPI Specification(前身為 Swagger)是描述 RESTful API 的標準格式。它讓你用 YAML 或 JSON 定義 API 的結構,並自動產生互動式文件。
自動產生 API 文件
常見的工具鏈:
- Swagger UI:基於 OpenAPI spec 產生互動式文件頁面
- Redoc:更美觀的文件呈現方式
- swagger-jsdoc:從程式碼中的 JSDoc 註解自動產生 OpenAPI spec
- tsoa / NestJS Swagger:從 TypeScript 裝飾器自動產生
API 測試工具
- Postman:最廣泛使用的 API 測試工具,支援集合、環境變數、自動化測試
- Insomnia:更輕量的替代方案,介面簡潔
- Thunder Client:VS Code 擴充套件,不離開編輯器就能測試 API
- curl / httpie:命令列工具,適合 CI/CD 整合
實務範例
範例一:Express.js RESTful Router
// routes/users.js
import { Router } from 'express';
import { authenticate } from '../middleware/auth.js';
import { validate } from '../middleware/validate.js';
import { createUserSchema, updateUserSchema } from '../schemas/user.js';
const router = Router();
// GET /v1/users - 取得使用者列表(支援分頁與過濾)
router.get('/', authenticate, async (req, res) => {
const { page = 1, limit = 20, status, sort = '-created_at' } = req.query;
const offset = (page - 1) * limit;
const filters = {};
if (status) filters.status = status;
const [users, total] = await Promise.all([
UserService.findAll({ filters, sort, limit, offset }),
UserService.count(filters),
]);
res.json({
data: users,
pagination: {
page: Number(page),
limit: Number(limit),
total,
totalPages: Math.ceil(total / limit),
},
});
});
// GET /v1/users/:id - 取得單一使用者
router.get('/:id', authenticate, async (req, res) => {
const user = await UserService.findById(req.params.id);
if (!user) {
return res.status(404).json({
error: {
code: 'USER_NOT_FOUND',
message: `使用者 ${req.params.id} 不存在`,
},
});
}
res.json({ data: user });
});
// POST /v1/users - 建立新使用者
router.post('/', authenticate, validate(createUserSchema), async (req, res) => {
const user = await UserService.create(req.body);
res.status(201).json({ data: user });
});
// PATCH /v1/users/:id - 部分更新使用者
router.patch('/:id', authenticate, validate(updateUserSchema), async (req, res) => {
const user = await UserService.update(req.params.id, req.body);
if (!user) {
return res.status(404).json({
error: {
code: 'USER_NOT_FOUND',
message: `使用者 ${req.params.id} 不存在`,
},
});
}
res.json({ data: user });
});
// DELETE /v1/users/:id - 刪除使用者
router.delete('/:id', authenticate, async (req, res) => {
const deleted = await UserService.delete(req.params.id);
if (!deleted) {
return res.status(404).json({
error: {
code: 'USER_NOT_FOUND',
message: `使用者 ${req.params.id} 不存在`,
},
});
}
res.status(204).send();
});
// GET /v1/users/:id/orders - 取得使用者的訂單
router.get('/:id/orders', authenticate, async (req, res) => {
const { cursor, limit = 20 } = req.query;
const orders = await OrderService.findByUserId(req.params.id, { cursor, limit });
res.json({
data: orders.items,
pagination: {
nextCursor: orders.nextCursor,
hasMore: orders.hasMore,
},
});
});
export default router;範例二:JWT 認證 Middleware
// middleware/auth.js
import jwt from 'jsonwebtoken';
const ACCESS_TOKEN_SECRET = process.env.JWT_ACCESS_SECRET;
const REFRESH_TOKEN_SECRET = process.env.JWT_REFRESH_SECRET;
// 產生 Token 對
export function generateTokens(user) {
const accessToken = jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role,
},
ACCESS_TOKEN_SECRET,
{ expiresIn: '15m' }
);
const refreshToken = jwt.sign(
{ sub: user.id },
REFRESH_TOKEN_SECRET,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
}
// 認證 Middleware
export function authenticate(req, res, next) {
// 從 Authorization Header 取得 Token
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: {
code: 'MISSING_TOKEN',
message: '未提供認證 Token',
},
});
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET);
req.user = {
id: decoded.sub,
email: decoded.email,
role: decoded.role,
};
next();
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({
error: {
code: 'TOKEN_EXPIRED',
message: 'Token 已過期,請使用 Refresh Token 換發新 Token',
},
});
}
return res.status(401).json({
error: {
code: 'INVALID_TOKEN',
message: 'Token 無效',
},
});
}
}
// 授權 Middleware(角色檢查)
export function authorize(...allowedRoles) {
return (req, res, next) => {
if (!req.user || !allowedRoles.includes(req.user.role)) {
return res.status(403).json({
error: {
code: 'FORBIDDEN',
message: '權限不足,無法執行此操作',
},
});
}
next();
};
}
// Refresh Token 端點
export async function refreshAccessToken(req, res) {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(400).json({
error: {
code: 'MISSING_REFRESH_TOKEN',
message: '未提供 Refresh Token',
},
});
}
try {
const decoded = jwt.verify(refreshToken, REFRESH_TOKEN_SECRET);
const user = await UserService.findById(decoded.sub);
if (!user) {
return res.status(401).json({
error: { code: 'USER_NOT_FOUND', message: '使用者不存在' },
});
}
const tokens = generateTokens(user);
res.json({ data: tokens });
} catch (err) {
return res.status(401).json({
error: { code: 'INVALID_REFRESH_TOKEN', message: 'Refresh Token 無效或已過期' },
});
}
}範例三:OpenAPI YAML 規格定義
# openapi.yaml
openapi: 3.0.3
info:
title: User Management API
description: 使用者管理 RESTful API
version: 1.0.0
contact:
name: API Support
email: api@example.com
servers:
- url: https://api.example.com/v1
description: Production
- url: https://staging-api.example.com/v1
description: Staging
paths:
/users:
get:
summary: 取得使用者列表
tags: [Users]
security:
- bearerAuth: []
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
maximum: 100
- name: status
in: query
schema:
type: string
enum: [active, inactive, suspended]
- name: sort
in: query
schema:
type: string
default: '-created_at'
responses:
'200':
description: 成功取得使用者列表
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
'401':
$ref: '#/components/responses/Unauthorized'
post:
summary: 建立新使用者
tags: [Users]
security:
- bearerAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserInput'
responses:
'201':
description: 使用者建立成功
'400':
$ref: '#/components/responses/BadRequest'
'409':
description: 使用者已存在
/users/{id}:
get:
summary: 取得單一使用者
tags: [Users]
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: 成功
'404':
$ref: '#/components/responses/NotFound'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
format: email
name:
type: string
role:
type: string
enum: [user, admin]
status:
type: string
enum: [active, inactive, suspended]
createdAt:
type: string
format: date-time
CreateUserInput:
type: object
required: [email, name, password]
properties:
email:
type: string
format: email
name:
type: string
minLength: 2
password:
type: string
minLength: 8
Pagination:
type: object
properties:
page:
type: integer
limit:
type: integer
total:
type: integer
totalPages:
type: integer
responses:
BadRequest:
description: 請求格式錯誤
Unauthorized:
description: 未認證或 Token 無效
NotFound:
description: 資源不存在範例四:Rate Limiting Middleware
// middleware/rateLimit.js
// 簡易的記憶體型速率限制(生產環境建議使用 Redis)
const requestCounts = new Map();
/**
* 建立速率限制 Middleware
* @param {Object} options
* @param {number} options.windowMs - 時間視窗(毫秒)
* @param {number} options.max - 時間視窗內最大請求數
* @param {string} options.message - 超限時的錯誤訊息
* @param {Function} options.keyGenerator - 產生識別 key 的函式
*/
export function rateLimit({
windowMs = 60 * 1000,
max = 100,
message = '請求過於頻繁,請稍後再試',
keyGenerator = (req) => req.ip,
} = {}) {
// 定期清理過期記錄
setInterval(() => {
const now = Date.now();
for (const [key, record] of requestCounts.entries()) {
if (now - record.windowStart > windowMs) {
requestCounts.delete(key);
}
}
}, windowMs);
return (req, res, next) => {
const key = keyGenerator(req);
const now = Date.now();
let record = requestCounts.get(key);
// 如果沒有記錄或時間視窗已過期,重新開始計數
if (!record || now - record.windowStart > windowMs) {
record = { count: 0, windowStart: now };
requestCounts.set(key, record);
}
record.count++;
// 設定速率限制相關 Header
const remaining = Math.max(0, max - record.count);
const resetTime = Math.ceil((record.windowStart + windowMs) / 1000);
res.set('X-RateLimit-Limit', String(max));
res.set('X-RateLimit-Remaining', String(remaining));
res.set('X-RateLimit-Reset', String(resetTime));
if (record.count > max) {
res.set('Retry-After', String(Math.ceil(windowMs / 1000)));
return res.status(429).json({
error: {
code: 'RATE_LIMIT_EXCEEDED',
message,
retryAfter: Math.ceil((record.windowStart + windowMs - now) / 1000),
},
});
}
next();
};
}
// 使用範例
// app.js
import express from 'express';
import { rateLimit } from './middleware/rateLimit.js';
const app = express();
// 全域速率限制:每分鐘 100 次
app.use(rateLimit({ windowMs: 60 * 1000, max: 100 }));
// 登入端點更嚴格:每 15 分鐘 5 次
app.use('/v1/auth/login', rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: '登入嘗試過於頻繁,請 15 分鐘後再試',
keyGenerator: (req) => `login:${req.ip}`,
}));常見問題與風險
1. API 命名不一致
問題: 團隊內不同工程師用不同風格命名 API,例如 /getUsers、/user-list、/fetch_orders。
風險: 前端工程師無法預測 API 路徑,增加聯調成本。
解法:
- 制定並文件化 API 命名規範(API Style Guide)
- 在 Code Review 中嚴格把關
- 使用 Linter 工具(如
spectral)檢查 OpenAPI spec 是否符合規範
2. JWT 儲存在 localStorage
問題: 許多教學文章將 JWT 存在 localStorage,但這極度不安全。
風險: 任何 XSS(跨站腳本攻擊)漏洞都能輕易竊取 localStorage 中的 Token。攻擊者只需注入一段 document.cookie 或 localStorage.getItem('token') 就能取得使用者的身份憑證。
解法:
- Access Token 存在記憶體中(JavaScript 變數)
- Refresh Token 存在
HttpOnly+Secure+SameSite=Strict的 Cookie 中 - 搭配 CSRF Token 防護
3. 沒有實作速率限制
問題: API 完全沒有速率限制,任何人都能無限制地呼叫。
風險:
- DDoS 攻擊造成服務癱瘓
- 暴力破解密碼
- 爬蟲大量抓取資料
- API 使用成本失控
解法:
- 全域速率限制 + 針對敏感端點的更嚴格限制
- 使用 API Gateway 層級的限制(如 AWS API Gateway、Nginx)
- 對認證相關端點實作指數退避(Exponential Backoff)
4. 缺少 API 版本管理
問題: API 變更直接影響所有客戶端,沒有版本隔離。
風險: 後端的一次 breaking change 可能導致所有前端應用同時崩潰。
解法:
- 從第一天就引入版本前綴
/v1/ - 新版本上線後,舊版本維持至少一個 deprecation 週期
- 在回應 Header 中加入
Deprecation和Sunset標頭通知客戶端
5. 錯誤回應格式不統一
問題: 不同端點回傳不同格式的錯誤,有的是 { message: "..." },有的是 { error: "..." },有的直接回傳字串。
風險: 前端無法用統一的攔截器處理錯誤,每個 API 呼叫都要寫特殊的錯誤處理邏輯。
解法:
- 定義全域的錯誤格式規範
- 實作全域錯誤處理 Middleware
- 所有錯誤都經過同一個格式化層再回傳
總結
API 設計不只是技術選擇,更是團隊協作的基礎。一份好的 API 設計能讓前後端並行開發、降低溝通成本、減少整合問題。記住以下原則:
- 一致性優先:命名、格式、錯誤處理都要統一
- 安全性不能妥協:正確使用 JWT、不把 Token 存在 localStorage、實作速率限制
- 文件即契約:用 OpenAPI 規範定義 API,自動產生文件
- 版本管理從第一天開始:未來的自己會感謝現在的決定
Proto 實踐對照
在 Django Proto 中,RESTful API 設計遵循本文的原則:使用 DRF ViewSet 實現資源路由、drf-spectacular 自動產生 Swagger 文件、統一的 { success, data, error } 回應格式、以及 Cursor-based 分頁。前端的 Vue 3 Proto 則用 Axios Interceptors 處理 Token 注入和 401 重試。詳見 Proto 規劃方法論。
延伸閱讀
- API Gateway:如何透過 API Gateway 統一管理路由、認證、限流
- Identity & Access:更深入的身份驗證與存取控制設計
- Secrets & Config:如何安全管理 JWT Secret、API Key 等機密設定