api-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAPI Design Guide
API设计指南
Best practices for designing developer-friendly, maintainable APIs.
打造开发者友好、可维护API的最佳实践。
REST API Principles
REST API设计原则
Resource Naming
资源命名
Use nouns, not verbs:
✓ GET /users
✓ GET /users/123
✓ GET /users/123/orders
✗ GET /getUsers
✗ GET /fetchUserById
✗ POST /createNewOrderUse plural nouns:
✓ /users
✓ /orders
✓ /products
✗ /user
✗ /orderHierarchical relationships:
/users/{userId}/orders # User's orders
/users/{userId}/orders/{orderId} # Specific order使用名词,而非动词:
✓ GET /users
✓ GET /users/123
✓ GET /users/123/orders
✗ GET /getUsers
✗ GET /fetchUserById
✗ POST /createNewOrder使用复数名词:
✓ /users
✓ /orders
✓ /products
✗ /user
✗ /order层级关系:
/users/{userId}/orders # 用户的订单
/users/{userId}/orders/{orderId} # 特定订单HTTP Methods
HTTP请求方法
| Method | Purpose | Idempotent | Request Body |
|---|---|---|---|
| GET | Retrieve resource(s) | Yes | No |
| POST | Create resource | No | Yes |
| PUT | Replace resource entirely | Yes | Yes |
| PATCH | Update resource partially | No | Yes |
| DELETE | Remove resource | Yes | No |
| 请求方法 | 用途 | 幂等性 | 请求体 |
|---|---|---|---|
| GET | 获取资源(单个或多个) | 是 | 否 |
| POST | 创建资源 | 否 | 是 |
| PUT | 完全替换资源 | 是 | 是 |
| PATCH | 部分更新资源 | 否 | 是 |
| DELETE | 删除资源 | 是 | 否 |
Status Codes
状态码
Success (2xx):
- - Request succeeded
200 OK - - Resource created (return Location header)
201 Created - - Success with no response body
204 No Content
Client Error (4xx):
- - Malformed request
400 Bad Request - - Authentication required
401 Unauthorized - - No permission
403 Forbidden - - Resource doesn't exist
404 Not Found - - State conflict
409 Conflict - - Validation failed
422 Unprocessable Entity - - Rate limited
429 Too Many Requests
Server Error (5xx):
- - Unexpected error
500 Internal Server Error - - Temporary outage
503 Service Unavailable
成功(2xx):
- - 请求成功
200 OK - - 资源已创建(返回Location响应头)
201 Created - - 请求成功,无响应体
204 No Content
客户端错误(4xx):
- - 请求格式错误
400 Bad Request - - 需要身份验证
401 Unauthorized - - 无访问权限
403 Forbidden - - 资源不存在
404 Not Found - - 状态冲突
409 Conflict - - 验证失败
422 Unprocessable Entity - - 请求频率超限
429 Too Many Requests
服务器错误(5xx):
- - 服务器意外错误
500 Internal Server Error - - 服务暂时不可用
503 Service Unavailable
Response Format
响应格式
Successful response:
json
{
"data": {
"id": "123",
"name": "John Doe",
"email": "john@example.com"
}
}Collection response:
json
{
"data": [
{ "id": "1", "name": "Item 1" },
{ "id": "2", "name": "Item 2" }
],
"meta": {
"page": 1,
"perPage": 20,
"total": 100,
"totalPages": 5
},
"links": {
"self": "/items?page=1",
"next": "/items?page=2",
"prev": null
}
}Error response:
json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "Must be a valid email address"
}
]
}
}成功响应:
json
{
"data": {
"id": "123",
"name": "John Doe",
"email": "john@example.com"
}
}集合响应:
json
{
"data": [
{ "id": "1", "name": "Item 1" },
{ "id": "2", "name": "Item 2" }
],
"meta": {
"page": 1,
"perPage": 20,
"total": 100,
"totalPages": 5
},
"links": {
"self": "/items?page=1",
"next": "/items?page=2",
"prev": null
}
}错误响应:
json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "验证失败",
"details": [
{
"field": "email",
"code": "INVALID_FORMAT",
"message": "必须是有效的邮箱地址"
}
]
}
}Pagination
分页
Offset-based (simple, but slow on large datasets):
GET /users?page=2&perPage=20
GET /users?offset=40&limit=20Cursor-based (efficient, recommended):
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20Response includes next cursor:
json
{
"data": [...],
"meta": {
"nextCursor": "eyJpZCI6MTQzfQ",
"hasMore": true
}
}基于偏移量(实现简单,但大数据集下性能差):
GET /users?page=2&perPage=20
GET /users?offset=40&limit=20基于游标(性能高效,推荐使用):
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20响应中包含下一页游标:
json
{
"data": [...],
"meta": {
"nextCursor": "eyJpZCI6MTQzfQ",
"hasMore": true
}
}Filtering, Sorting, Fields
过滤、排序、字段选择
Filtering:
GET /users?status=active
GET /users?created_after=2024-01-01
GET /users?role=admin,moderatorSorting:
GET /users?sort=name
GET /users?sort=-created_at # Descending
GET /users?sort=status,-created_at # Multiple fieldsField selection:
GET /users?fields=id,name,email
GET /users?include=orders,profile过滤:
GET /users?status=active
GET /users?created_after=2024-01-01
GET /users?role=admin,moderator排序:
GET /users?sort=name
GET /users?sort=-created_at # 降序
GET /users?sort=status,-created_at # 多字段排序字段选择:
GET /users?fields=id,name,email
GET /users?include=orders,profileVersioning
版本控制
URL path (recommended):
/api/v1/users
/api/v2/usersHeader:
Accept: application/vnd.api+json;version=2URL路径(推荐):
/api/v1/users
/api/v2/users请求头:
Accept: application/vnd.api+json;version=2OpenAPI Specification
OpenAPI规范
yaml
openapi: 3.0.3
info:
title: My API
version: 1.0.0
description: API for managing users
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List users
tags: [Users]
parameters:
- name: page
in: query
schema:
type: integer
default: 1
responses:
"200":
description: List of users
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: string
email:
type: string
format: email
name:
type: stringyaml
openapi: 3.0.3
info:
title: My API
version: 1.0.0
description: API for managing users
servers:
- url: https://api.example.com/v1
paths:
/users:
get:
summary: List users
tags: [Users]
parameters:
- name: page
in: query
schema:
type: integer
default: 1
responses:
"200":
description: List of users
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/User"
components:
schemas:
User:
type: object
required: [id, email]
properties:
id:
type: string
email:
type: string
format: email
name:
type: stringAuthentication
身份验证
API Keys (simple, for server-to-server):
Authorization: Api-Key YOUR_API_KEYBearer Tokens (JWT, OAuth):
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...Include in OpenAPI:
yaml
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []API密钥(实现简单,适用于服务间调用):
Authorization: Api-Key YOUR_API_KEYBearer令牌(JWT、OAuth):
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...在OpenAPI中配置:
yaml
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []Rate Limiting
请求频率限制
Include headers in responses:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200Return when exceeded.
429 Too Many Requests在响应中返回相关头信息:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200当请求超限后返回。
429 Too Many RequestsSecurity Checklist
安全检查清单
- HTTPS only
- Authentication on protected routes
- Input validation
- Output encoding
- Rate limiting
- CORS configuration
- No sensitive data in URLs
- Audit logging
- 仅使用HTTPS
- 受保护路由需身份验证
- 输入验证
- 输出编码
- 请求频率限制
- CORS配置
- URL中不包含敏感数据
- 审计日志
gRPC and Protocol Buffers
gRPC与Protocol Buffers
Proto Definition
Proto定义
protobuf
syntax = "proto3";
package user.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (User);
rpc StreamUpdates(StreamRequest) returns (stream UserUpdate);
}
message User {
string id = 1;
string name = 2;
string email = 3;
google.protobuf.Timestamp created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}protobuf
syntax = "proto3";
package user.v1;
service UserService {
rpc GetUser(GetUserRequest) returns (User);
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser(CreateUserRequest) returns (User);
rpc StreamUpdates(StreamRequest) returns (stream UserUpdate);
}
message User {
string id = 1;
string name = 2;
string email = 3;
google.protobuf.Timestamp created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
message ListUsersResponse {
repeated User users = 1;
string next_page_token = 2;
}When to Use gRPC vs REST
gRPC与REST的适用场景对比
| Factor | gRPC | REST |
|---|---|---|
| Performance | Binary, fast | JSON, human-readable |
| Streaming | Bidirectional | SSE/WebSocket workaround |
| Type safety | Proto generates types | OpenAPI + codegen |
| Browser | Needs gRPC-Web proxy | Native |
| Tooling | Protoc, Buf | Swagger, Postman |
| Best for | Service-to-service, streaming | Public APIs, web clients |
| 考量因素 | gRPC | REST |
|---|---|---|
| 性能 | 二进制协议,速度快 | JSON格式,人类可读 |
| 流处理 | 支持双向流 | 需借助SSE/WebSocket实现 |
| 类型安全 | 通过Proto生成类型定义 | 依赖OpenAPI+代码生成 |
| 浏览器支持 | 需要gRPC-Web代理 | 原生支持 |
| 工具链 | Protoc、Buf | Swagger、Postman |
| 最佳适用场景 | 服务间调用、流处理 | 公开API、Web客户端 |
tRPC for TypeScript
TypeScript的tRPC框架
typescript
// server/router.ts
import { router, publicProcedure, protectedProcedure } from './trpc';
import { z } from 'zod';
export const appRouter = router({
user: router({
get: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
create: protectedProcedure
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
}))
.mutation(async ({ input, ctx }) => {
return db.user.create({ data: { ...input, createdBy: ctx.userId } });
}),
}),
});
export type AppRouter = typeof appRouter;
// client.ts - Full type inference, no codegen
const user = trpc.user.get.useQuery({ id: '123' });
const createUser = trpc.user.create.useMutation();tRPC is ideal for monorepo full-stack TypeScript apps where client and server share the same codebase.
typescript
// server/router.ts
import { router, publicProcedure, protectedProcedure } from './trpc';
import { z } from 'zod';
export const appRouter = router({
user: router({
get: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return db.user.findUnique({ where: { id: input.id } });
}),
create: protectedProcedure
.input(z.object({
name: z.string().min(1),
email: z.string().email(),
}))
.mutation(async ({ input, ctx }) => {
return db.user.create({ data: { ...input, createdBy: ctx.userId } });
}),
}),
});
export type AppRouter = typeof appRouter;
// client.ts - 完整类型推导,无需代码生成
const user = trpc.user.get.useQuery({ id: '123' });
const createUser = trpc.user.create.useMutation();tRPC适用于客户端与服务器共享代码库的单体仓库全栈TypeScript项目。
Webhook Design Patterns
Webhook设计模式
Webhook Payload
Webhook负载
json
{
"id": "evt_abc123",
"type": "order.completed",
"created_at": "2025-01-15T10:30:00Z",
"data": {
"order_id": "ord_456",
"total": 99.99,
"currency": "USD"
}
}json
{
"id": "evt_abc123",
"type": "order.completed",
"created_at": "2025-01-15T10:30:00Z",
"data": {
"order_id": "ord_456",
"total": 99.99,
"currency": "USD"
}
}Signature Verification
签名验证
typescript
// Sign webhooks with HMAC-SHA256
import crypto from 'crypto';
function signWebhook(payload: string, secret: string): string {
return crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
}
// Verify on receiving end
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = signWebhook(payload, secret);
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}typescript
// 使用HMAC-SHA256签名Webhook
import crypto from 'crypto';
function signWebhook(payload: string, secret: string): string {
return crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
}
// 接收端验证签名
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = signWebhook(payload, secret);
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected),
);
}Retry Strategy
重试策略
Attempt 1: Immediately
Attempt 2: After 1 minute
Attempt 3: After 5 minutes
Attempt 4: After 30 minutes
Attempt 5: After 2 hours
Attempt 6: After 24 hours (final)
Failed webhooks: log, alert, manual retry UI第1次:立即重试
第2次:1分钟后
第3次:5分钟后
第4次:30分钟后
第5次:2小时后
第6次:24小时后(最后一次)
失败的Webhook:记录日志、触发告警、提供手动重试界面Idempotency
幂等性
typescript
// Include idempotency key in webhook
// Receivers should deduplicate based on event ID
async function handleWebhook(event: WebhookEvent) {
// Check if already processed
const existing = await db.processedEvents.findUnique({
where: { eventId: event.id },
});
if (existing) return { status: 'already_processed' };
// Process and record
await db.$transaction([
processEvent(event),
db.processedEvents.create({ data: { eventId: event.id } }),
]);
}typescript
// 在Webhook中包含幂等键
// 接收端需根据事件ID去重
async function handleWebhook(event: WebhookEvent) {
// 检查是否已处理
const existing = await db.processedEvents.findUnique({
where: { eventId: event.id },
});
if (existing) return { status: 'already_processed' };
// 处理并记录
await db.$transaction([
processEvent(event),
db.processedEvents.create({ data: { eventId: event.id } }),
]);
}API Versioning Strategies
API版本控制策略对比
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL path | | Explicit, easy to route | URL pollution |
| Query parameter | | Optional parameter | Easy to miss |
| Header | | Clean URLs | Hidden, harder to test |
| Content negotiation | | Standards-based | Complex to implement |
Recommendation: URL path versioning for simplicity. Only bump major versions for breaking changes. Use additive, non-breaking changes within a version.
| 策略 | 示例 | 优势 | 劣势 |
|---|---|---|---|
| URL路径 | | 明确、易于路由 | URL冗余 |
| 查询参数 | | 可选参数 | 容易遗漏 |
| 请求头 | | URL简洁 | 隐藏式、测试难度高 |
| 内容协商 | | 基于标准协议 | 实现复杂 |
推荐方案:使用URL路径版本控制,实现简单直观。仅在发生破坏性变更时升级主版本号,版本内仅做兼容式的新增变更。