API 是你的产品与世界的接口。选错风格不会立刻致命,但会在规模增长时变成持续的技术债。这不是宗教战争——每种风格都有明确的适用场景。
资源导向,HTTP 语义,最广泛支持。公开 API 的默认选择。
查询导向,客户端决定数据形状。复杂前端 + 多数据源的利器。
端到端类型推断,零代码生成。全栈 TypeScript 项目的最佳 DX。
Protocol Buffers,双向流。微服务内部通信的首选。
REST(Representational State Transfer)不是框架,不是库,是架构风格。它的核心思想:一切皆资源,用 HTTP 动词操作资源。
| 约束 | 含义 | 实际意义 |
|---|---|---|
| Client-Server | 前后端分离 | 独立演进,前端换框架不影响 API |
| Stateless | 请求自带全部上下文 | 水平扩展容易,无需 sticky session |
| Cacheable | 响应必须声明可缓存性 | ETag / Cache-Control 减少重复请求 |
| Uniform Interface | 统一接口约定 | HTTP 动词 + 资源 URI + 状态码 |
| Layered System | 客户端不知道是否直连服务器 | 可插入 CDN / 网关 / 负载均衡 |
| Code on Demand 可选 | 服务器可返回可执行代码 | JS widget、WebAssembly 模块 |
# ✅ 好的设计:名词复数,层级清晰
GET /api/v1/users # 用户列表
GET /api/v1/users/123 # 单个用户
GET /api/v1/users/123/orders # 用户 123 的订单
POST /api/v1/users/123/orders # 为用户 123 创建订单
# ❌ 坏的设计:动词路径,非 RESTful
GET /api/v1/getUsers
POST /api/v1/createOrder
POST /api/v1/deleteUser/123
# ❌ 嵌套过深(超过 2 层就该重新设计)
GET /api/v1/users/123/orders/456/items/789/refunds
# CRUD 映射
GET /api/v1/articles # 列表(可过滤/分页)
POST /api/v1/articles # 创建
GET /api/v1/articles/42 # 详情
PATCH /api/v1/articles/42 # 部分更新(推荐)
PUT /api/v1/articles/42 # 全量替换(少用)
DELETE /api/v1/articles/42 # 删除
# 特殊动作(非 CRUD)
POST /api/v1/articles/42/publish # 发布(状态变更)
POST /api/v1/articles/42/duplicate # 复制(创建新资源)
POST /api/v1/exports # 异步导出(返回 202)
# 分页 — Cursor-based(推荐,适合无限滚动)
GET /api/v1/articles?cursor=eyJpZCI6MTAwfQ&limit=20
# 分页 — Offset-based(适合跳页)
GET /api/v1/articles?page=2&page_size=20
# 过滤
GET /api/v1/articles?status=published&author_id=42
# 排序
GET /api/v1/articles?sort=-created_at,title
# - 表示降序,逗号分隔多字段
# 字段选择(sparse fieldsets)
GET /api/v1/articles?fields=id,title,created_at
# 搜索
GET /api/v1/articles?q=REST+API+design
// 单个资源
{
"data": {
"id": "art_1a2b3c",
"type": "article",
"title": "API 设计指南",
"status": "published",
"created_at": "2025-01-15T08:30:00Z",
"updated_at": "2025-01-16T14:20:00Z"
}
}
// 列表(带分页元信息)
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTIzfQ",
"has_more": true,
"total_count": 1234
}
}
// 错误
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数验证失败",
"details": [
{ "field": "email", "message": "邮箱格式不正确" }
]
}
}
获取用户列表 → N 次请求获取每个用户的头像和角色。这是 REST 最被诟病的地方:
// 前端:拿到用户列表后,逐个获取关联数据
GET /api/v1/users // 1 次
GET /api/v1/users/1/avatar // N 次
GET /api/v1/users/2/avatar
GET /api/v1/users/3/avatar
...
// 解决方案 1:include 参数
GET /api/v1/users?include=avatar,role
// 解决方案 2:字段展开
GET /api/v1/users?expand=avatar_url,role_name
// 解决方案 3:如果这真的是常态 → 考虑 GraphQL
fields 参数让客户端选择字段,或者干脆用 GraphQL。
GraphQL 是 Facebook 开发的查询语言,核心理念:客户端精确描述需要什么数据,服务器只返回那些数据。一个 endpoint,无限可能。
type User {
id: ID!
email: String!
name: String
avatar: Image
orders(filter: OrderFilter): [Order!]!
created_at: DateTime!
}
type Order {
id: ID!
items: [OrderItem!]!
total: Float!
status: OrderStatus!
user: User!
}
enum OrderStatus {
PENDING
PAID
SHIPPED
DELIVERED
CANCELLED
}
input OrderFilter {
status: OrderStatus
created_after: DateTime
}
type Query {
user(id: ID!): User
users(filter: UserFilter, first: Int, after: String): UserConnection!
order(id: ID!): Order
me: User!
}
type Mutation {
createOrder(input: CreateOrderInput!): Order!
updateOrder(id: ID!, input: UpdateOrderInput!): Order!
cancelOrder(id: ID!): Order!
}
type Subscription {
orderUpdated(id: ID!): Order!
newOrder: Order!
}
# 精确获取:只要名字和邮箱,不要别的
query {
user(id: "usr_abc123") {
name
email
orders(first: 5) {
edges {
node {
id
total
status
}
}
}
}
}
# 同一个 Schema,不同的查询需求
query {
me {
name
avatar { url }
}
}
# 没有 DataLoader:100 个用户 → 100 次 DB 查询获取头像
query {
users(first: 100) {
avatar { url } # 每个 user 触发一次 DB 查询!
}
}
# 解决:DataLoader 批量加载
const avatarLoader = new DataLoader(async (userIds) => {
const avatars = await db.query(
'SELECT * FROM avatars WHERE user_id IN (?)',
[userIds]
);
return userIds.map(id => avatars.find(a => a.user_id === id));
});
# 恶意查询:递归嵌套耗尽服务器资源
query {
users { # 100 个
orders { # 每个 50 个 = 5000
items { # 每个 10 个 = 50000
product {
reviews { # 每个 100 个 = 5000000
user { orders { items { ... } } }
}
}
}
}
}
}
# 解决方案:
# 1. 查询深度限制 (depth limiting)
# 2. 复杂度分析 (query complexity analysis)
# 3. 持久化查询 (persisted queries) — 只允许预注册的查询
# 4. 超时 + 结果大小限制
# Apollo Server 示例
import { depthLimit } from 'graphql-depth-limit';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)], # 最大嵌套 5 层
});
# REST 可以直接用 HTTP 缓存:ETag、Cache-Control
GET /api/v1/users/123 → 200 OK, ETag: "abc", Cache-Control: max-age=300
# GraphQL 只有一个 endpoint,无法用 URL 区分缓存
POST /graphql { query: "{ user(id: 123) { name } }" }
# 解决方案:
# 1. Apollo Client — normalized cache (按 __typename:id 缓存)
# 2. 持久化查询 — GET 请求 + hash → 可用 CDN 缓存
# 3. @cacheControl 指令 — 细粒度缓存控制
type User @cacheControl(maxAge: 300) {
id: ID!
name: String
email: String @cacheControl(maxAge: 0, scope: PRIVATE)
}
| 场景 | 选 GraphQL? | 原因 |
|---|---|---|
| 复杂前端,多数据源 | ✅ 强烈推荐 | 一个查询聚合多个微服务 |
| 移动端,带宽敏感 | ✅ 推荐 | 精确获取,无过度获取 |
| 公开 API | ⚠️ 谨慎 | REST 更易理解,生态更广 |
| 简单 CRUD | ❌ 过度 | REST 更简单 |
| 内部服务间通信 | ❌ 不合适 | gRPC 更好 |
| 全栈 TS 项目 | ⚠️ 考虑 tRPC | GraphQL 的类型安全需要代码生成 |
tRPC 让你在零代码生成、零手动同步的情况下,实现前后端类型安全。后端定义 router,前端直接调用——像调用本地函数一样调用远程 API。
// ===== 后端:定义 router =====
import { initTRPC, TRPCError } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC().create();
// 公开路由
const publicProcedure = t.procedure;
// 需认证路由
const authedProcedure = t.procedure.use(async ({ ctx, next }) => {
if (!ctx.user) throw new TRPCError({ code: 'UNAUTHORIZED' });
return next({ ctx: { user: ctx.user } });
});
const appRouter = t.router({
// 查询用户列表
listUsers: publicProcedure
.input(z.object({
limit: z.number().min(1).max(100).default(20),
cursor: z.string().optional(),
}))
.query(async ({ input, ctx }) => {
const users = await ctx.db.user.findMany({
take: input.limit + 1,
cursor: input.cursor ? { id: input.cursor } : undefined,
orderBy: { created_at: 'desc' },
});
return {
items: users.slice(0, input.limit),
next_cursor: users.length > input.limit ? users[users.length - 1].id : undefined,
};
}),
// 创建文章(需认证)
createArticle: authedProcedure
.input(z.object({
title: z.string().min(1).max(200),
content: z.string().min(1),
tags: z.array(z.string()).max(5).default([]),
}))
.mutation(async ({ input, ctx }) => {
return ctx.db.article.create({
data: { ...input, author_id: ctx.user.id },
});
}),
});
export type AppRouter = typeof appRouter;
// ===== 前端:直接调用,完整类型推断 =====
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from '../server/router';
const trpc = createTRPCProxyClient<AppRouter>({
links: [httpBatchLink({ url: '/api/trpc' })],
});
// 调用后端函数——自动补全、类型检查、编译时错误
const result = await trpc.listUsers.query({
limit: 10,
cursor: undefined,
});
// result.items: 自动推断为 User[]
// result.next_cursor: 自动推断为 string | undefined
const article = await trpc.createArticle.mutate({
title: 'tRPC 入门',
content: '端到端类型安全...',
});
// 如果写错字段名 → 编译时直接报错!
| 维度 | tRPC | GraphQL |
|---|---|---|
| 类型安全 | ✅ 原生 TS 推断,零代码生成 | ⚠️ 需要 graphql-codegen |
| 学习曲线 | ✅ 会 TS 就会 tRPC | ❌ Schema 语言 + Resolver + DataLoader |
| 跨语言 | ❌ 只支持 TS/JS | ✅ 任何语言都有客户端库 |
| 公开 API | ❌ 不适合 | ✅ 天然支持 |
| 开发速度 | ✅ 极快,改后端前端立刻知道 | ⚠️ 中等,需要同步 Schema |
| 缓存 | ⚠️ React Query / SWR | ⚠️ Apollo Cache / 持久化查询 |
| 生态 | ⚠️ 较新,Next.js 生态为主 | ✅ 成熟,社区大 |
| Batch | ✅ httpBatchLink 自动合并请求 | ⚠️ 需要 Batch Http Link |
| 订阅 | ✅ WebSocket 链接 | ✅ Subscription 原生支持 |
gRPC 是 Google 开源的高性能 RPC 框架,使用 Protocol Buffers 序列化,支持双向流、流控、超时、认证。它是微服务间通信的黄金标准。
// order.proto
syntax = "proto3";
package order;
service OrderService {
// 普通 RPC
rpc GetOrder(GetOrderRequest) returns (Order);
rpc ListOrders(ListOrdersRequest) returns (stream Order);
// 客户端流
rpc BulkCreateOrders(stream CreateOrderRequest) returns (BulkCreateResponse);
// 双向流
rpc OrderChat(stream OrderMessage) returns (stream OrderMessage);
}
message GetOrderRequest {
string order_id = 1;
}
message ListOrdersRequest {
int32 page_size = 1;
string page_token = 2;
string filter = 3;
}
message Order {
string id = 1;
string user_id = 2;
repeated OrderItem items = 3;
double total = 4;
string status = 5;
int64 created_at = 6; // Unix timestamp
}
message OrderItem {
string product_id = 1;
int32 quantity = 2;
double price = 3;
}
import grpc
from concurrent import futures
import order_pb2
import order_pb2_grpc
class OrderServicer(order_pb2_grpc.OrderServiceServicer):
def GetOrder(self, request, context):
order = db.get_order(request.order_id)
if not order:
context.abort(grpc.StatusCode.NOT_FOUND, "Order not found")
return order_pb2.Order(
id=order.id,
user_id=order.user_id,
total=order.total,
status=order.status,
)
def ListOrders(self, request, context):
# 服务端流:逐个推送订单
orders = db.list_orders(
page_size=request.page_size,
page_token=request.page_token,
)
for order in orders:
yield order_pb2.Order(
id=order.id,
user_id=order.user_id,
total=order.total,
status=order.status,
)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
order_pb2_grpc.add_OrderServiceServicer_to_server(
OrderServicer(), server
)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
| 维度 | gRPC | REST + JSON |
|---|---|---|
| 序列化 | Protocol Buffers (二进制) | JSON (文本) |
| payload 大小 | 小 3-10 倍 | 较大 |
| 序列化速度 | 快 5-20 倍 | 较慢 |
| 流式支持 | ✅ 四种流模式 | ⚠️ SSE / WebSocket |
| 代码生成 | ✅ 自动生成客户端 | ❌ 手动或 OpenAPI Gen |
| 浏览器支持 | ❌ 需要 gRPC-Web 代理 | ✅ 原生支持 |
| 调试 | ⚠️ 需要专用工具 | ✅ curl / 浏览器 |
| 人可读 | ❌ 二进制格式 | ✅ 文本格式 |
grpc-gateway(gRPC → REST 代理)或 envoy(gRPC-Web 代理)暴露 RESTful 接口给前端。这意味着 gRPC 主要用于服务间通信。
版本管理是 API 设计中最容易被忽视、但影响最深远的决策。选错策略,未来的每次变更都是噩梦。
| 策略 | 方式 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| URI 版本 | /api/v1/ | 简单直观 | URL 变更,SEO 影响 | 公开 API 最常见 |
| Header 版本 | Accept: application/vnd.api.v2+json | URL 不变 | 不直观,调试难 | GitHub API 式 |
| 查询参数 | ?version=2 | 简单 | 缓存问题 | 不推荐 |
| 无版本 (Evolvability) | 只增不删,向后兼容 | 最优雅 | 需要强纪律 | Stripe API 式 |
# 大版本变更:URI
/api/v1/users → /api/v2/users
# v2 内的向后兼容变更:不改版本号
# ✅ 新增可选字段
# ✅ 新增 endpoint
# ✅ 新增查询参数
# ❌ 删除字段 → 需要新大版本
# ❌ 改变字段类型 → 需要新大版本
# ❌ 重命名字段 → 需要新大版本
# v1 → v2 迁移期:同时维护两个版本
# 迁移时间:至少 6 个月
# 通知方式:响应头 Sunset: Sat, 1 Nov 2025 00:00:00 GMT
# 通知方式:响应头 Deprecation: true
// ✅ 好的做法:新增字段,旧客户端忽略
// v1 响应
{ "id": 1, "name": "Alice" }
// v1 + 新字段(向后兼容)
{ "id": 1, "name": "Alice", "avatar_url": "..." }
// ❌ 坏的做法:重命名字段
// 旧
{ "name": "Alice" }
// 新(破坏性变更!)
{ "full_name": "Alice" }
// ✅ 修复:同时返回两个字段,旧字段标记 deprecated
{ "name": "Alice", "full_name": "Alice" }
// 几个月后移除 name 字段,升版本到 v2
API 安全的核心问题:你是谁(认证)+ 你能做什么(授权)。这部分与 认证授权 模块深度关联,这里聚焦 API 层面的实现。
| 方式 | 机制 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| API Key | Header / Query | 简单 | 无用户概念,无法过期 | 服务间、公开 API |
| JWT | Bearer Token | 无状态,自包含 | 无法即时吊销 | 前后端分离 |
| Session Cookie | 服务端 Session | 安全,可即时吊销 | 有状态,扩展复杂 | 传统 Web 应用 |
| OAuth2 | 授权码 / 客户端凭证 | 第三方授权 | 复杂 | 第三方登录、API 授权 |
| mTLS | 双向 TLS 证书 | 最强安全 | 证书管理复杂 | 服务间、零信任 |
// JWT 结构
// Header: { alg: "RS256", typ: "JWT" }
// Payload: { sub: "usr_abc123", exp: 1700000000, iat: 1699999000, scope: "read write" }
// Signature: RS256(header.payload, private_key)
// Access Token + Refresh Token 双令牌模式
const accessToken = jwtSign(
{ sub: user.id, scope: 'read write' },
{ expiresIn: '15m' } // 短命:15 分钟
);
const refreshToken = jwtSign(
{ sub: user.id, type: 'refresh' },
{ expiresIn: '7d' } // 长命:7 天
);
// 中间件:验证 Access Token
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Missing token' });
try {
const decoded = jwtVerify(token, PUBLIC_KEY, { algorithms: ['RS256'] });
// 检查 Token 是否被吊销(黑名单)
const isRevoked = await redis.get(`revoked:${decoded.jti}`);
if (isRevoked) return res.status(401).json({ error: 'Token revoked' });
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
}
algorithms: ['RS256'],不要用 none// API Key 格式
// 前缀 + 随机部分,方便识别来源
sk_live_1a2b3c4d5e6f7g8h9i0j // Stripe 风格
key_xxxx_yyyy_zzzz // 自定义风格
// 存储:只存 hash,不存明文
const crypto = require('crypto');
const keyHash = crypto.createHash('sha256').update(apiKey).digest('hex');
// 请求时验证
async function validateApiKey(key) {
const hash = sha256(key);
const apiKeyRecord = await db.apiKey.findUnique({ where: { hash } });
if (!apiKeyRecord) return null;
if (apiKeyRecord.expires_at && apiKeyRecord.expires_at < new Date()) return null;
return apiKeyRecord;
}
好的错误处理让 API 从"能用"升级到"好用"。差的错误处理让集成者痛苦不堪。
// 标准错误响应(RFC 7807 inspired)
{
"error": {
"type": "https://example.com/errors/validation-error",
"title": "请求验证失败",
"status": 400,
"detail": "邮箱格式不正确",
"instance": "/api/v1/users",
"request_id": "req_abc123",
"timestamp": "2025-01-15T08:30:00Z",
"details": [
{
"field": "email",
"message": "邮箱格式不正确",
"value": "not-an-email"
}
]
}
}
| 状态码 | 含义 | 何时使用 |
|---|---|---|
| 200 | OK | GET 成功,PUT/PATCH 更新成功 |
| 201 | Created | POST 创建成功,返回新资源 |
| 202 | Accepted | 异步操作已接受(导出、批量处理) |
| 204 | No Content | DELETE 成功,无返回体 |
| 400 | Bad Request | 请求参数错误、验证失败 |
| 401 | Unauthorized | 未认证(没 token 或 token 无效) |
| 403 | Forbidden | 已认证但无权限 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 冲突(重复创建、版本冲突) |
| 422 | Unprocessable Entity | 格式正确但语义错误 |
| 429 | Too Many Requests | 限流,返回 Retry-After |
| 500 | Internal Server Error | 服务器错误,不暴露内部信息 |
| 503 | Service Unavailable | 维护、过载,返回 Retry-After |
401 是"我不知道你是谁"(认证问题),403 是"我知道你是谁但你不能做这个"(授权问题)。不要混淆!
import { TRPCError } from '@trpc/server';
// 抛出类型安全错误
throw new TRPCError({
code: 'NOT_FOUND', // 自动映射到 HTTP 404
message: '文章不存在',
cause: originalError,
});
// 错误码映射
// PARSE_ERROR → 400
// BAD_REQUEST → 400
// UNAUTHORIZED → 401
// FORBIDDEN → 403
// NOT_FOUND → 404
// METHOD_NOT_SUPPORTED → 405
// TIMEOUT → 408
// CONFLICT → 409
// PRECONDITION_FAILED → 412
// PAYLOAD_TOO_LARGE → 413
// TOO_MANY_REQUESTS → 429
// INTERNAL_SERVER_ERROR → 500
每个公开 API 都需要限流。没有限流 = 一个恶意用户就能打垮你的服务。
| 算法 | 原理 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| 固定窗口 | 每 N 秒计数器重置 | 简单 | 窗口边界突刺 | 简单场景 |
| 滑动窗口 | 最近 N 秒的请求数 | 平滑 | 内存消耗大 | 精准限流 |
| 令牌桶 | 固定速率放入令牌 | 允许突发 | 实现稍复杂 | API 限流首选 |
| 漏桶 | 固定速率处理请求 | 均匀输出 | 不允许突发 | 流量整形 |
-- rate_limit.lua
local key = KEYS[1]
local capacity = tonumber(ARGV[1]) -- 桶容量
local refill_rate = tonumber(ARGV[2]) -- 每秒补充令牌数
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4]) -- 本次请求令牌数
local bucket = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(bucket[1])
local last_refill = tonumber(bucket[2])
if tokens == nil then
tokens = capacity
last_refill = now
end
-- 补充令牌
local elapsed = now - last_refill
local new_tokens = math.min(capacity, tokens + elapsed * refill_rate)
-- 消耗令牌
local allowed = false
if new_tokens >= requested then
new_tokens = new_tokens - requested
allowed = true
end
redis.call('HMSET', key, 'tokens', new_tokens, 'last_refill', now)
redis.call('EXPIRE', key, math.ceil(capacity / refill_rate) + 1)
return { allowed and 1 or 0, math.floor(new_tokens) }
# 响应头告诉客户端限流状态
X-RateLimit-Limit: 100 # 窗口内允许的请求数
X-RateLimit-Remaining: 67 # 剩余请求数
X-RateLimit-Reset: 1700001000 # 窗口重置时间 (Unix)
Retry-After: 30 # 被限流时,多久后重试 (秒)
在研究 20 个 AI Agent 框架时,我们发现 API 设计有一些独特的挑战和模式,值得所有 SaaS 开发者学习。
LLM 生成是流式的,API 必须支持。这不只是 AI 场景——任何长耗时操作都应该考虑流式。
// Server-Sent Events (SSE) — 最简单的流式方案
// 服务端
app.get('/api/v1/chat/stream', async (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const stream = await llm.chat({
messages: req.body.messages,
stream: true,
});
for await (const chunk of stream) {
res.write(`data: ${JSON.stringify(chunk)}\n\n`);
}
res.write('data: [DONE]\n\n');
res.end();
});
// 客户端
const eventSource = new EventSource('/api/v1/chat/stream');
eventSource.onmessage = (event) => {
if (event.data === '[DONE]') return;
const chunk = JSON.parse(event.data);
appendToken(chunk.content);
};
Agent 需要通过 API 调用工具。OpenAI 的 Function Calling 已经成为事实标准:
// 工具定义
{
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
},
"required": ["city"]
}
}
}
]
}
// LLM 返回工具调用请求
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": '{"city":"北京","unit":"celsius"}'
}
}]
}
}]
}
// openapi.yaml
openapi: 3.1.0
info:
title: SaaS Platform API
version: 1.0.0
paths:
/api/v1/users:
get:
summary: 获取用户列表
parameters:
- name: limit
in: query
schema: { type: integer, default: 20, maximum: 100 }
- name: cursor
in: query
schema: { type: string }
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/UserListResponse'
| 工具 | 语言 | 方式 | 特色 |
|---|---|---|---|
| Swagger UI | 多语言 | OpenAPI Spec | 可交互文档,行业标准 |
| Scalar | 多语言 | OpenAPI Spec | 现代 UI,比 Swagger 好看 |
| Redoc | 多语言 | OpenAPI Spec | 三栏布局,适合阅读 |
| tRPC Docs | TS | 自动推断 | 零配置,自动生成 |
| FastAPI Swagger | Python | 装饰器 | 自动生成 OpenAPI + Swagger UI |
| Mintlify | 多语言 | MDX | 最美文档站,付费 |
// 请求日志格式
{
"request_id": "req_abc123",
"method": "GET",
"path": "/api/v1/users",
"status": 200,
"duration_ms": 45,
"user_id": "usr_xyz",
"ip": "1.2.3.4",
"user_agent": "Mozilla/5.0...",
"timestamp": "2025-01-15T08:30:00.123Z"
}
// 响应头注入可追踪信息
X-Request-Id: req_abc123 // 每个请求唯一 ID
X-Response-Time: 45ms // 响应耗时
X-RateLimit-Limit: 100 // 限流信息
X-RateLimit-Remaining: 67
真实世界中,很多成功的 SaaS 同时使用多种 API 风格。这是一个典型的混合架构:
/api/v1/*
→
🌍 公开 API + 第三方/api/trpc/*
→
🖥️ 内部前端:50051
→
⚙️ 微服务间通信
// Next.js App Router 混合路由
// app/api/v1/[...path]/route.ts → REST handler
// app/api/trpc/[trpc]/route.ts → tRPC handler
// Nginx 路由配置
# /api/v1/* → Node.js REST server
# /api/trpc/* → tRPC server
# 内部 gRPC 不暴露,只在 K8s 集群内通信
server {
location /api/v1/ {
proxy_pass http://rest-server:3001;
}
location /api/trpc/ {
proxy_pass http://trpc-server:3002;
}
# gRPC 服务不在 Nginx 暴露
}
| ✅ | 检查项 |
|---|---|
| ⬜ | API 有版本管理策略 |
| ⬜ | 所有 endpoint 都有认证保护(除公开的) |
| ⬜ | 错误响应格式统一 |
| ⬜ | 列表 endpoint 支持分页(cursor 优先) |
| ⬜ | 有限流保护 |
| ⬜ | 有 OpenAPI 文档 |
| ⬜ | 敏感操作有二次确认 |
| ⬜ | 请求有 Request-Id 可追踪 |
| ⬜ | CORS 配置正确 |
| ⬜ | 长操作有异步模式(202 + 轮询/Webhook) |
| ⬜ | 响应字段可选择性返回(fields 参数) |
| ⬜ | 大列表默认分页,不会一次返回全部 |
| 如果你不想用… | 考虑替代 | 原因 |
|---|---|---|
| REST | tRPC (全 TS) / GraphQL (复杂查询) | 更少 boilerplate / 更灵活 |
| GraphQL | REST + include 参数 / tRPC | 减少复杂度 / 更好的类型安全 |
| tRPC | REST + OpenAPI (需公开 API) | 跨语言支持 |
| gRPC | REST (简单场景) / tRPC (TS 全栈) | 更简单 / 更好的浏览器支持 |
| Swagger UI | Scalar / Redoc | 更好的 UI |
| 自建限流 | Upstash Ratelimit / Cloudflare Rate Limiting | 托管服务,免运维 |