api-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API 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 /createNewOrder
Use plural nouns:
✓ /users
✓ /orders
✓ /products

✗ /user
✗ /order
Hierarchical 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请求方法

MethodPurposeIdempotentRequest Body
GETRetrieve resource(s)YesNo
POSTCreate resourceNoYes
PUTReplace resource entirelyYesYes
PATCHUpdate resource partiallyNoYes
DELETERemove resourceYesNo

请求方法用途幂等性请求体
GET获取资源(单个或多个)
POST创建资源
PUT完全替换资源
PATCH部分更新资源
DELETE删除资源

Status Codes

状态码

Success (2xx):
  • 200 OK
    - Request succeeded
  • 201 Created
    - Resource created (return Location header)
  • 204 No Content
    - Success with no response body
Client Error (4xx):
  • 400 Bad Request
    - Malformed request
  • 401 Unauthorized
    - Authentication required
  • 403 Forbidden
    - No permission
  • 404 Not Found
    - Resource doesn't exist
  • 409 Conflict
    - State conflict
  • 422 Unprocessable Entity
    - Validation failed
  • 429 Too Many Requests
    - Rate limited
Server Error (5xx):
  • 500 Internal Server Error
    - Unexpected error
  • 503 Service Unavailable
    - Temporary outage

成功(2xx)
  • 200 OK
    - 请求成功
  • 201 Created
    - 资源已创建(返回Location响应头)
  • 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=20
Cursor-based (efficient, recommended):
GET /users?cursor=eyJpZCI6MTIzfQ&limit=20
Response 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,moderator
Sorting:
GET /users?sort=name
GET /users?sort=-created_at         # Descending
GET /users?sort=status,-created_at  # Multiple fields
Field 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,profile

Versioning

版本控制

URL path (recommended):
/api/v1/users
/api/v2/users
Header:
Accept: application/vnd.api+json;version=2

URL路径(推荐):
/api/v1/users
/api/v2/users
请求头
Accept: application/vnd.api+json;version=2

OpenAPI 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: string

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: string

Authentication

身份验证

API Keys (simple, for server-to-server):
Authorization: Api-Key YOUR_API_KEY
Bearer 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_KEY
Bearer令牌(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: 1640995200
Return
429 Too Many Requests
when exceeded.

在响应中返回相关头信息:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1640995200
当请求超限后返回
429 Too Many Requests

Security 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的适用场景对比

FactorgRPCREST
PerformanceBinary, fastJSON, human-readable
StreamingBidirectionalSSE/WebSocket workaround
Type safetyProto generates typesOpenAPI + codegen
BrowserNeeds gRPC-Web proxyNative
ToolingProtoc, BufSwagger, Postman
Best forService-to-service, streamingPublic APIs, web clients

考量因素gRPCREST
性能二进制协议,速度快JSON格式,人类可读
流处理支持双向流需借助SSE/WebSocket实现
类型安全通过Proto生成类型定义依赖OpenAPI+代码生成
浏览器支持需要gRPC-Web代理原生支持
工具链Protoc、BufSwagger、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版本控制策略对比

StrategyExampleProsCons
URL path
/api/v1/users
Explicit, easy to routeURL pollution
Query parameter
/api/users?version=1
Optional parameterEasy to miss
Header
Accept: application/vnd.api.v1
Clean URLsHidden, harder to test
Content negotiation
Accept: application/json;v=2
Standards-basedComplex to implement
Recommendation: URL path versioning for simplicity. Only bump major versions for breaking changes. Use additive, non-breaking changes within a version.
策略示例优势劣势
URL路径
/api/v1/users
明确、易于路由URL冗余
查询参数
/api/users?version=1
可选参数容易遗漏
请求头
Accept: application/vnd.api.v1
URL简洁隐藏式、测试难度高
内容协商
Accept: application/json;v=2
基于标准协议实现复杂
推荐方案:使用URL路径版本控制,实现简单直观。仅在发生破坏性变更时升级主版本号,版本内仅做兼容式的新增变更。