Skip to content

RESTful API 设计

REST 架构原则

REST(Representational State Transfer)是一种软件架构风格,由 Roy Fielding 在 2000 年的博士论文中提出。它不是一个标准,而是一组设计约束和原则,用于构建分布式超媒体系统。

REST 核心约束

┌───────────────────────────────────────────────────┐
│                     REST 约束                    │
├───────────────────┬───────────────────────────────┤
│ 1. 客户端-服务器  │ 分离关注点,提高可移植性     │
│ 2. 无状态         │ 每个请求包含所有必要信息     │
│ 3. 缓存           │ 可缓存响应,提高性能        │
│ 4. 分层系统       │ 中间层处理请求,隐藏复杂性   │
│ 5. 统一接口       │ 资源识别、操作标准化        │
│ 6. 按需编码(可选)│ 服务端可向客户端发送代码     │
└───────────────────┴───────────────────────────────┘

资源与表示

  • 资源:REST 架构的核心概念,任何可以被标识的实体都可以是资源(用户、文章、订单等)
  • 资源标识符:URI(Uniform Resource Identifier),如 /users/123
  • 表示:资源的具体表现形式,如 JSON、XML、HTML 等
  • 状态转移:通过 HTTP 方法对资源进行操作,实现状态的转换

HTTP 方法语义

HTTP 方法语义幂等性安全性适用场景
GET读取资源获取用户列表、文章详情
POST创建资源注册用户、创建订单
PUT替换资源更新用户信息、替换文章
PATCH修改资源部分更新用户信息
DELETE删除资源删除用户、删除订单
HEAD只获取响应头检查资源是否存在
OPTIONS获取支持的方法CORS 预检请求

URL 设计规范

资源命名原则

  1. 使用复数名词:资源是集合,如 /users 而不是 /user
  2. 使用 kebab-case:如 /user-profiles 而不是 /userProfiles/user_profiles
  3. 使用具体名称:如 /articles 而不是 /items
  4. 避免动词:资源用名词表示,操作通过 HTTP 方法体现

路径结构

┌───────────────────────────────────────────────────┐
│                URL 路径结构                      │
├───────────────────┬───────────────────────────────┤
│ /resources        │ 资源集合                     │
│ /resources/{id}   │ 单个资源                     │
│ /resources/{id}/subresources | 嵌套资源         │
└───────────────────┴───────────────────────────────┘

路径参数 vs 查询参数

  • 路径参数:用于标识资源,如 /users/{id} 中的 id
  • 查询参数:用于过滤、排序、分页等,如 /users?page=1&limit=10&sort=created_at:desc

嵌套资源

  • 推荐:使用扁平结构,如 /articles/comments 分开
  • 可选:使用嵌套结构表示层级关系,如 /articles/{id}/comments

HTTP 状态码

状态码分类

分类范围含义示例
信息性100-199服务器收到请求,需要继续处理100 Continue
成功200-299请求成功200 OK, 201 Created, 204 No Content
重定向300-399需要进一步操作才能完成请求301 Moved Permanently, 302 Found, 304 Not Modified
客户端错误400-499客户端请求有问题400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found
服务器错误500-599服务器处理请求时出错500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable

常见状态码使用场景

  • 200 OK:请求成功,返回资源
  • 201 Created:创建资源成功,返回新资源的 URI
  • 204 No Content:请求成功,但无需返回内容(如删除操作)
  • 400 Bad Request:请求参数错误
  • 401 Unauthorized:未提供认证信息或认证失败
  • 403 Forbidden:认证成功,但无权访问
  • 404 Not Found:资源不存在
  • 405 Method Not Allowed:不支持的 HTTP 方法
  • 429 Too Many Requests:请求过于频繁,超出限流
  • 500 Internal Server Error:服务器内部错误

请求与响应设计

请求体格式

  • 推荐使用 JSONContent-Type: application/json
  • 请求体示例
json
{
  "name": "John Doe",
  "email": "john@example.com",
  "age": 30
}

统一响应结构

json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com"
  }
}

错误响应格式

json
{
  "code": 400,
  "message": "Invalid request",
  "errors": [
    {
      "field": "email",
      "message": "Email is required"
    }
  ]
}

内容协商

  • Accept:客户端期望的响应格式,如 Accept: application/json
  • Content-Type:客户端发送的请求体格式,如 Content-Type: application/json

API 版本控制

版本控制策略

策略示例优点缺点
URL 路径版本/v1/users清晰可见,易于缓存URL 变更频繁
请求头版本X-API-Version: 1URL 保持不变不易于缓存,调试复杂
查询参数版本/users?version=1实现简单易于被忽略,缓存困难

版本迁移策略

  1. 向后兼容:新版本支持旧版本的请求格式
  2. 废弃通知:在响应头中添加 X-API-Deprecated 标识
  3. 过渡期:旧版本和新版本并行运行一段时间
  4. 终止支持:明确旧版本的终止日期

认证与授权

认证方式

方式描述适用场景
API Key在请求头或查询参数中传递 API Key服务器间通信
Basic Auth用户名和密码 Base64 编码内部系统,开发环境
JWT无状态令牌,包含用户信息前后端分离,移动应用
OAuth 2.0授权框架,支持第三方登录第三方应用集成

授权模型

  • RBAC(基于角色的访问控制):用户 → 角色 → 权限
  • ABAC(基于属性的访问控制):基于用户、资源、环境等属性的规则

安全最佳实践

传输安全

  • 使用 HTTPS:加密传输数据
  • HTTP 严格传输安全(HSTS):强制使用 HTTPS

输入验证

  • 使用验证库:Joi、Zod、class-validator
  • 验证所有输入:路径参数、查询参数、请求体

CORS 配置

javascript
const cors = require('cors');

app.use(cors({
  origin: ['https://example.com'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

CSRF 防护

  • 使用 CSRF Token:在表单中包含 CSRF Token
  • 验证 Origin/Referer:检查请求来源

请求限速

  • 使用 Redis 实现:滑动窗口或令牌桶算法
  • 设置合理的限速规则:如每分钟 60 次请求

API 文档

OpenAPI/Swagger

  • OpenAPI 3.0:API 描述规范
  • Swagger UI:交互式 API 文档

文档生成工具

  • swagger-jsdoc:从 JSDoc 注释生成文档
  • @nestjs/swagger:Nest.js 集成 Swagger
  • Postman:手动创建和测试 API 文档

文档示例

yaml
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
paths:
  /users:
    get:
      summary: Get all users
      responses:
        '200':
          description: A list of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string

性能优化

缓存策略

  • HTTP 缓存:设置 Cache-Control
  • 服务器缓存:使用 Redis 缓存热点数据
  • 客户端缓存:浏览器本地存储

批量操作

  • 批量获取:如 /users?ids=1,2,3
  • 批量创建:一次请求创建多个资源

字段选择

  • fields 参数:如 /users?fields=id,name,email
  • 减少响应数据:只返回必要的字段

分页优化

  • 偏移分页/users?page=1&limit=10
  • 游标分页/users?cursor=abc&limit=10(适用于大数据集)

GraphQL 简介

与 REST 对比

特性RESTGraphQL
请求方式多个端点,多个请求单一端点,一次请求
响应结构固定结构客户端定义结构
过度获取常见避免
不足获取常见避免
文档需额外维护内建
缓存简单(HTTP 缓存)复杂(需要客户端实现)

核心概念

  • Schema:定义 API 的类型和操作
  • Query:获取数据(类似 GET)
  • Mutation:修改数据(类似 POST/PUT/DELETE)
  • Subscription:实时数据(类似 WebSocket)

示例

graphql
# Schema
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

type Post {
  id: ID!
  title: String!
  content: String!
  author: User!
}

type Query {
  users: [User!]!
  user(id: ID!): User!
  posts: [Post!]!
}

# Query
query {
  users {
    id
    name
    email
  }
}

面试高频问题

1. 什么是 REST?REST 有哪些核心约束?

回答思路

  • REST 是一种软件架构风格,不是标准
  • 核心约束包括:客户端-服务器、无状态、缓存、分层系统、统一接口、按需编码
  • 统一接口是最核心的约束,包括资源识别、操作标准化、自描述消息、超媒体作为应用状态的引擎(HATEOAS)

2. HTTP 方法的幂等性和安全性是什么意思?

回答思路

  • 幂等性:多次相同的请求产生相同的结果,如 GET、PUT、DELETE
  • 安全性:请求不会修改服务器状态,如 GET、HEAD、OPTIONS
  • POST 和 PATCH 既不是幂等的也不是安全的

3. 如何设计 RESTful API 的 URL?

回答思路

  • 使用复数名词:/users 而不是 /user
  • 使用 kebab-case:/user-profiles 而不是 /userProfiles
  • 避免动词:用 HTTP 方法表示操作
  • 路径参数用于标识资源,查询参数用于过滤、排序、分页
  • 合理使用嵌套资源,但不要过深

4. 如何处理 API 版本控制?

回答思路

  • URL 路径版本:/v1/users(最常用)
  • 请求头版本:X-API-Version: 1
  • 查询参数版本:/users?version=1
  • 建议使用 URL 路径版本,清晰可见,易于缓存
  • 版本迁移策略:向后兼容、废弃通知、过渡期、终止支持

5. 如何设计统一的响应格式?

回答思路

  • 成功响应:包含 code(状态码)、message(消息)、data(数据)
  • 错误响应:包含 code(状态码)、message(错误消息)、errors(详细错误信息)
  • 保持响应格式一致,便于客户端处理

6. API 安全有哪些最佳实践?

回答思路

  • 使用 HTTPS 加密传输
  • 实现认证和授权(JWT、OAuth 2.0)
  • 输入验证(使用 Joi、Zod 等库)
  • CORS 配置(限制允许的来源)
  • CSRF 防护(使用 CSRF Token)
  • 请求限速(防止暴力攻击)
  • 敏感数据处理(加密存储)

7. 如何优化 API 性能?

回答思路

  • 缓存策略(HTTP 缓存、服务器缓存、客户端缓存)
  • 批量操作(减少请求次数)
  • 字段选择(只返回必要字段)
  • 分页优化(偏移分页、游标分页)
  • 数据库优化(索引、查询优化)
  • 异步处理(处理耗时操作)

8. REST 和 GraphQL 有什么区别?

回答思路

  • 请求方式:REST 多个端点,GraphQL 单一端点
  • 响应结构:REST 固定结构,GraphQL 客户端定义结构
  • 过度获取/不足获取:REST 常见,GraphQL 避免
  • 文档:REST 需额外维护,GraphQL 内建
  • 缓存:REST 简单(HTTP 缓存),GraphQL 复杂
  • 适用场景:REST 适用于简单 API,GraphQL 适用于复杂 API

延伸阅读

官方文档

书籍

  • 《RESTful Web APIs》by Leonard Richardson
  • 《API Design Patterns》by JJ Geewax
  • 《GraphQL in Action》by Samer Buna

在线资源

工具

用心学习,用代码说话 💻