CodeL
以前端为翼,以 AI 为脑,向全栈而行
2026-03-31

RESTful API 完全指南

RESTful API 完全指南:从概念到实战 一篇文章彻底理解 RESTful API:设计原则、HTTP 方法、状态码、实战示例、最佳实践 一、核心概念 1.1 什么是 REST? REST = Representa...

RESTful API 完全指南:从概念到实战 #

一篇文章彻底理解 RESTful API:设计原则、HTTP 方法、状态码、实战示例、最佳实践


一、核心概念 #

1.1 什么是 REST? #

REST = Representational State Transfer(表述性状态转移)

大白话解释:REST 是一种软件架构风格,规定了如何设计 Web API,让 API 简洁、统一、易于理解。

打个比方:

你去餐厅点餐,菜单告诉你:

  • 看菜单(GET)—— 获取菜品信息
  • 点菜(POST)—— 创建一个订单
  • 改口味(PUT)—— 修改订单内容
  • 取消订单(DELETE)—— 删除订单

RESTful API 就是这样的"菜单规则",让客户端和服务端用统一的方式交互。

1.2 RESTful API 是什么? #

RESTful API = 符合 REST 原则的 API

核心特点:

特点 说明 例子
资源为中心 URL 代表资源,不是动作 /users(用户资源)
统一接口 用 HTTP 方法表示操作 GET 获取,POST 创建
无状态 每个请求包含所有信息 不依赖 session
可缓存 响应可以缓存 GET 请求可缓存

1.3 REST 与传统 API 对比 #

传统 RPC 风格:

# 动作在 URL 中,不统一
/getAllUsers
/createUser
/updateUser?id=1
/deleteUser?id=1
/getUserById?id=1
 
# 问题:
# 1. URL 命名混乱(每个人写法不同)
# 2. 动词混在 URL 中
# 3. 不符合 HTTP 方法语义

RESTful 风格:

# 资源为中心,HTTP 方法表示操作
GET    /users           # 获取所有用户
GET    /users/1         # 获取 ID=1 的用户
POST   /users           # 创建新用户
PUT    /users/1         # 更新 ID=1 的用户(完整更新)
PATCH  /users/1         # 更新 ID=1 的用户(部分更新)
DELETE /users/1         # 删除 ID=1 的用户
 
# 优势:
# 1. URL 清晰统一(都是 /users)
# 2. HTTP 方法语义明确
# 3. 易于理解和维护

二、HTTP 方法详解 #

2.1 RESTful 使用的 HTTP 方法 #

RESTful API 主要使用 5 种 HTTP 方法:

方法 操作 幂等性 安全性 示例
GET 获取资源 ✅ 幂等 ✅ 安全 获取用户列表
POST 创建资源 ❌ 不幂等 ❌ 不安全 创建新用户
PUT 完整更新 ✅ 幂等 ❌ 不安全 更新用户全部信息
PATCH 部分更新 ❌ 不幂等* ❌ 不安全 更新用户邮箱
DELETE 删除资源 ✅ 幂等 ❌ 不安全 删除用户

名词解释:

  • 幂等:多次执行相同请求,结果相同。如:删除 ID=1 的用户,删一次和删十次结果一样(用户没了)
  • 安全:请求不会修改资源。GET 只读取,不修改,所以是安全的

2.2 GET —— 获取资源 #

GET 用于读取资源,不修改数据。

# 获取所有用户
GET /users
 
# 响应
HTTP/1.1 200 OK
Content-Type: application/json
 
{
  "users": [
    { "id": 1, "name": "张三", "email": "zhang@example.com" },
    { "id": 2, "name": "李四", "email": "li@example.com" }
  ],
  "total": 2
}
 
# 获取单个用户
GET /users/1
 
# 响应
HTTP/1.1 200 OK
{
  "id": 1,
  "name": "张三",
  "email": "zhang@example.com",
  "createdAt": "2026-01-15"
}
 
# 获取用户的订单
GET /users/1/orders
 
# 响应
HTTP/1.1 200 OK
{
  "orders": [
    { "id": 101, "product": "手机", "price": 999 },
    { "id": 102, "product": "电脑", "price": 1999 }
  ]
}

GET 的特点:

  • 幂等:多次请求返回相同结果(数据不变的情况下)
  • 安全:只读取,不修改
  • 可缓存:浏览器可以缓存 GET 响应
  • 参数在 URL:/users?name=张三&page=1

2.3 POST —— 创建资源 #

POST 用于创建新资源。

# 创建新用户
POST /users
Content-Type: application/json
 
{
  "name": "王五",
  "email": "wang@example.com"
}
 
# 响应(返回创建的资源 + 201 状态码)
HTTP/1.1 201 Created
Location: /users/3
 
{
  "id": 3,
  "name": "王五",
  "email": "wang@example.com",
  "createdAt": "2026-04-01"
}
 
# 创建用户的订单
POST /users/1/orders
Content-Type: application/json
 
{
  "product": "平板",
  "price": 599
}
 
# 响应
HTTP/1.1 201 Created
Location: /users/1/orders/103
 
{
  "id": 103,
  "userId": 1,
  "product": "平板",
  "price": 599,
  "createdAt": "2026-04-01"
}

POST 的特点:

  • 不幂等:多次创建会生成多个资源
  • 不安全:会修改数据
  • 不可缓存
  • 数据在请求体:不在 URL 中显示
  • 成功返回 201 Created + Location 头(新资源 URL)

2.4 PUT —— 完整更新资源 #

PUT 用于完整更新资源,需要提供资源的全部字段。

# 完整更新用户(必须提供所有字段)
PUT /users/1
Content-Type: application/json
 
{
  "name": "张三更新",
  "email": "zhang_new@example.com",
  "age": 25,
  "phone": "13800138000"
}
 
# 响应
HTTP/1.1 200 OK
 
{
  "id": 1,
  "name": "张三更新",
  "email": "zhang_new@example.com",
  "age": 25,
  "phone": "13800138000",
  "updatedAt": "2026-04-01"
}
 
# 如果缺少字段,会被清除!
PUT /users/1
Content-Type: application/json
 
{
  "name": "张三"
}
 
# 响应(email、age、phone 都没了!)
HTTP/1.1 200 OK
{
  "id": 1,
  "name": "张三",
  "updatedAt": "2026-04-01"
}

PUT 的特点:

  • 幂等:多次更新同一数据,结果相同
  • 不安全:会修改数据
  • 完整替换:缺少的字段会被清空
  • 用于整体更新场景

2.5 PATCH —— 部分更新资源 #

PATCH 用于部分更新资源,只修改提供的字段。

# 只更新用户的邮箱
PATCH /users/1
Content-Type: application/json
 
{
  "email": "new_email@example.com"
}
 
# 响应(其他字段不变)
HTTP/1.1 200 OK
 
{
  "id": 1,
  "name": "张三",           // 不变
  "email": "new_email@example.com",  // 更新了
  "age": 24,               // 不变
  "phone": "13900139000",  // 不变
  "updatedAt": "2026-04-01"
}
 
# 更新多个字段
PATCH /users/1
Content-Type: application/json
 
{
  "email": "another@example.com",
  "age": 26
}
 
# 响应(name、phone 不变)
HTTP/1.1 200 OK
{
  "id": 1,
  "name": "张三",
  "email": "another@example.com",
  "age": 26,
  "updatedAt": "2026-04-01"
}

PATCH 的特点:

  • 通常不幂等(部分更新逻辑可能不同)
  • 不安全:会修改数据
  • 部分更新:只修改提供的字段
  • 用于单个字段更新场景

PUT vs PATCH 对比:

方法 更新方式 缺少字段 适用场景
PUT 完整替换 被清空 整体更新、替换资源
PATCH 部分更新 不变 单字段更新、修改属性

2.6 DELETE —— 删除资源 #

DELETE 用于删除资源。

# 删除单个用户
DELETE /users/1
 
# 响应(可选返回被删除的资源)
HTTP/1.1 204 No Content
 
# 或返回被删除的资源
HTTP/1.1 200 OK
{
  "id": 1,
  "name": "张三",
  "deletedAt": "2026-04-01"
}
 
# 删除用户的某个订单
DELETE /users/1/orders/101
 
# 响应
HTTP/1.1 204 No Content

DELETE 的特点:

  • 幂等:删除一次和删除多次结果相同
  • 不安全:会修改数据
  • 成功返回 204 No Content 或 200 OK

三、URL 设计规范 #

3.1 URL 基本规则 #

规则一:使用名词,不用动词

# ❌ 错误(动词在 URL)
/getUsers
/createUser
/deleteUser/1
 
# ✅ 正确(名词代表资源)
/users
/users/1

规则二:使用复数形式

# ❌ 错误(单数)
/user
/user/1
 
# ✅ 正确(复数)
/users
/users/1
 
# 原因:/users 表示"用户集合",更符合资源概念

规则三:层级关系用嵌套

# 获取用户的所有订单
GET /users/1/orders
 
# 获取用户的某个订单
GET /users/1/orders/101
 
# 创建用户的订单
POST /users/1/orders
 
# 一般不超过 3 层嵌套
/users/1/orders/101/items  # 第 3 层

规则四:用路径参数,不用查询参数

# ❌ 错误(查询参数表示资源)
GET /users?id=1
DELETE /users?id=1
 
# ✅ 正确(路径参数)
GET /users/1
DELETE /users/1
 
# 查询参数用于过滤、分页
GET /users?name=张三&page=1

3.2 URL 示例对照表 #

操作 传统写法 RESTful 写法
获取所有用户 /getAllUsers GET /users
获取单个用户 /getUserById?id=1 GET /users/1
创建用户 /createUser POST /users
更新用户 /updateUser?id=1 PUT /users/1
删除用户 /deleteUser?id=1 DELETE /users/1
获取用户订单 /getUserOrders?userId=1 GET /users/1/orders
搜索用户 /searchUsers?name=张 GET /users?name=张

3.3 查询参数设计 #

查询参数用于过滤、排序、分页

# 过滤
GET /users?status=active
GET /users?role=admin
GET /products?category=electronics&price_min=100&price_max=500
 
# 排序
GET /users?sort=name          # 按名字升序
GET /users?sort=-name         # 按名字降序(- 表示降序)
GET /users?sort=name,-age     # 先按名字升序,再按年龄降序
 
# 分页
GET /users?page=1&limit=10    # 第 1 页,每页 10 条
GET /users?offset=0&limit=10  # 从第 0 条开始,取 10 条
 
# 字段选择(只返回需要的字段)
GET /users?fields=id,name,email
 
# 搜索
GET /users?q=张三             # 关键词搜索
 
# 组合使用
GET /users?status=active&sort=-createdAt&page=1&limit=20

3.4 URL 最佳实践 #

# 使用小写字母
GET /Users        # ❌
GET /users        # ✅
 
# 用连字符分隔单词
GET /userProfiles  # ❌
GET /user-profiles # ✅
 
# 避免文件扩展名
GET /users.json    # ❌
GET /users         # ✅(Content-Type 决定格式)
 
# 版本号用 v 前缀
GET /v1/users      # ✅
GET /v2/users      # ✅(新版本)
 
# 资源 ID 用整数或 UUID
GET /users/1       # ✅(整数)
GET /users/abc123  # ✅(UUID 或字符串 ID)

四、HTTP 状态码详解 #

4.1 RESTful API 常用状态码 #

HTTP 状态码分为 5 类:

类别 范围 说明
1xx 100-199 信息性响应(很少用)
2xx 200-299 成功响应 ✅
3xx 300-399 重定向
4xx 400-499 客户端错误 ❌
5xx 500-599 服务端错误 ❌

4.2 成功响应(2xx) #

状态码 说明 使用场景
200 OK 成功 GET、PUT、PATCH、DELETE 成功
201 Created 已创建 POST 创建资源成功
202 Accepted 已接受 异步任务已接受,正在处理
204 No Content 无内容 DELETE 成功,不返回内容
206 Partial Content 部分内容 分块下载、范围请求
# GET 成功
GET /users/1
HTTP/1.1 200 OK
{ "id": 1, "name": "张三" }
 
# POST 创建成功
POST /users
HTTP/1.1 201 Created
Location: /users/3
{ "id": 3, "name": "王五" }
 
# DELETE 成功
DELETE /users/1
HTTP/1.1 204 No Content
# 无响应体
 
# 异步任务
POST /reports/generate
HTTP/1.1 202 Accepted
{ "taskId": "abc123", "status": "processing" }

4.3 客户端错误(4xx) #

状态码 说明 使用场景
400 Bad Request 请求格式错误 参数缺失、格式不对
401 Unauthorized 未认证 未登录、token 无效
403 Forbidden 禁止访问 无权限访问该资源
404 Not Found 未找到 资源不存在
405 Method Not Allowed 方法不允许 POST 访问只支持 GET 的 URL
409 Conflict 冲突 资源已存在、版本冲突
422 Unprocessable Entity 无法处理 参数验证失败
429 Too Many Requests 请求过多 超过速率限制
# 参数错误
POST /users
HTTP/1.1 400 Bad Request
{
  "error": "参数错误",
  "details": {
    "name": "名称不能为空",
    "email": "邮箱格式不正确"
  }
}
 
# 未登录
GET /users/profile
HTTP/1.1 401 Unauthorized
{
  "error": "请先登录",
  "code": "UNAUTHORIZED"
}
 
# 无权限
DELETE /users/1
HTTP/1.1 403 Forbidden
{
  "error": "无权删除该用户",
  "code": "FORBIDDEN"
}
 
# 资源不存在
GET /users/999
HTTP/1.1 404 Not Found
{
  "error": "用户不存在",
  "code": "NOT_FOUND"
}
 
# 方法不允许
POST /users/1  # 这个 URL 只支持 GET、PUT、DELETE
HTTP/1.1 405 Method Not Allowed
Allow: GET, PUT, DELETE
{
  "error": "不支持 POST 方法"
}
 
# 资源已存在
POST /users
HTTP/1.1 409 Conflict
{
  "error": "邮箱已被注册",
  "conflictField": "email"
}

4.4 服务端错误(5xx) #

状态码 说明 使用场景
500 Internal Server Error 内部错误 服务器代码错误
501 Not Implemented 未实现 功能未开发
502 Bad Gateway 网关错误 上游服务不可用
503 Service Unavailable 服务不可用 服务维护、过载
504 Gateway Timeout 网关超时 上游服务响应超时
# 服务器错误
GET /users
HTTP/1.1 500 Internal Server Error
{
  "error": "服务器内部错误",
  "code": "INTERNAL_ERROR",
  "requestId": "req-abc123"
}
 
# 服务不可用
GET /users
HTTP/1.1 503 Service Unavailable
Retry-After: 3600
{
  "error": "服务维护中,请稍后访问"
}

4.5 状态码选择指南 #

操作 成功状态码 失败状态码
GET 200 OK 404(不存在)、401/403(权限)
POST 201 Created 400(参数错误)、409(冲突)、401/403
PUT 200 OK 或 204 400、404、409、401/403
PATCH 200 OK 400、404、401/403
DELETE 204 No Content 404、401/403

五、请求与响应格式 #

5.1 Content-Type #

RESTful API 通常使用 JSON 格式:

# 请求头
Content-Type: application/json
 
# 请求体
{
  "name": "张三",
  "email": "zhang@example.com"
}
 
# 响应头
Content-Type: application/json
 
# 响应体
{
  "id": 1,
  "name": "张三",
  "email": "zhang@example.com"
}

常用 Content-Type:

Content-Type 说明 使用场景
application/json JSON 格式 最常用 ✅
application/xml XML 格式 传统系统
multipart/form-data 表单 + 文件 上传文件
application/x-www-form-urlencoded 表单数据 传统表单

5.2 响应结构设计 #

单资源响应:

{
  "id": 1,
  "name": "张三",
  "email": "zhang@example.com",
  "createdAt": "2026-01-15T08:30:00Z",
  "updatedAt": "2026-04-01T10:00:00Z"
}

列表响应:

{
  "data": [
    { "id": 1, "name": "张三" },
    { "id": 2, "name": "李四" },
    { "id": 3, "name": "王五" }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 50,
    "totalPages": 5
  }
}

错误响应:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "参数验证失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      },
      {
        "field": "age",
        "message": "年龄必须大于 0"
      }
    ]
  },
  "requestId": "req-abc123",
  "timestamp": "2026-04-01T10:00:00Z"
}

5.3 HATEOAS(可选进阶) #

HATEOAS = Hypermedia As The Engine Of Application State

在响应中包含相关链接,让客户端知道下一步可以做什么:

{
  "id": 1,
  "name": "张三",
  "email": "zhang@example.com",
  "_links": {
    "self": { "href": "/users/1" },
    "orders": { "href": "/users/1/orders" },
    "update": { "href": "/users/1", "method": "PUT" },
    "delete": { "href": "/users/1", "method": "DELETE" }
  }
}

六、实战示例 #

6.1 用户管理 API 设计 #

# 用户资源 API 设计文档
 
# 基础路径
Base URL: https://api.example.com/v1
 
# 用户资源
/users
 
# 操作列表
 
## 获取所有用户
GET /users
请求参数:
  - page: 页码(默认 1)
  - limit: 每页条数(默认 10)
  - sort: 排序字段
  - status: 状态筛选
响应: 200 OK + 用户列表
 
## 获取单个用户
GET /users/{id}
响应: 200 OK + 用户详情
错误: 404 Not Found
 
## 创建用户
POST /users
请求体: { "name", "email", "password" }
响应: 201 Created + Location 头 + 新用户
错误: 400 Bad Request, 409 Conflict
 
## 更新用户
PUT /users/{id}
请求体: 用户完整信息
响应: 200 OK
错误: 400, 404, 403
 
## 部分更新
PATCH /users/{id}
请求体: 要更新的字段
响应: 200 OK
错误: 400, 404, 403
 
## 删除用户
DELETE /users/{id}
响应: 204 No Content
错误: 404, 403

6.2 Express.js 实现示例 #

// Express.js RESTful API 示例
const express = require('express');
const app = express();
 
app.use(express.json());
 
// 模拟数据
let users = [
  { id: 1, name: '张三', email: 'zhang@example.com' },
  { id: 2, name: '李四', email: 'li@example.com' }
];
 
let nextId = 3;
 
// 获取所有用户
app.get('/users', (req, res) => {
  const { page = 1, limit = 10, sort, status } = req.query;
  
  // 过滤
  let result = users;
  if (status) {
    result = result.filter(u => u.status === status);
  }
  
  // 排序
  if (sort) {
    const [field, order] = sort.startsWith('-') 
      ? [sort.slice(1), -1] 
      : [sort, 1];
    result.sort((a, b) => (a[field] > b[field] ? order : -order));
  }
  
  // 分页
  const start = (page - 1) * limit;
  const paginated = result.slice(start, start + limit);
  
  res.json({
    data: paginated,
    pagination: {
      page: parseInt(page),
      limit: parseInt(limit),
      total: result.length,
      totalPages: Math.ceil(result.length / limit)
    }
  });
});
 
// 获取单个用户
app.get('/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  
  if (!user) {
    return res.status(404).json({
      error: {
        code: 'NOT_FOUND',
        message: '用户不存在'
      }
    });
  }
  
  res.json(user);
});
 
// 创建用户
app.post('/users', (req, res) => {
  const { name, email } = req.body;
  
  // 验证
  if (!name || !email) {
    return res.status(400).json({
      error: {
        code: 'VALIDATION_ERROR',
        message: '参数验证失败',
        details: [
          { field: 'name', message: '名称不能为空' },
          { field: 'email', message: '邮箱不能为空' }
        ]
      }
    });
  }
  
  // 检查邮箱是否已存在
  if (users.some(u => u.email === email)) {
    return res.status(409).json({
      error: {
        code: 'CONFLICT',
        message: '邮箱已被注册'
      }
    });
  }
  
  // 创建用户
  const newUser = {
    id: nextId++,
    name,
    email,
    createdAt: new Date().toISOString()
  };
  
  users.push(newUser);
  
  res.status(201)
    .location(`/users/${newUser.id}`)
    .json(newUser);
});
 
// 更新用户(PUT - 完整更新)
app.put('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const index = users.findIndex(u => u.id === id);
  
  if (index === -1) {
    return res.status(404).json({
      error: { code: 'NOT_FOUND', message: '用户不存在' }
    });
  }
  
  const { name, email } = req.body;
  
  // 验证
  if (!name || !email) {
    return res.status(400).json({
      error: { code: 'VALIDATION_ERROR', message: '必须提供所有字段' }
    });
  }
  
  // 完整替换
  users[index] = {
    id,
    name,
    email,
    updatedAt: new Date().toISOString()
  };
  
  res.json(users[index]);
});
 
// 部分更新(PATCH)
app.patch('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);
  
  if (!user) {
    return res.status(404).json({
      error: { code: 'NOT_FOUND', message: '用户不存在' }
    });
  }
  
  // 只更新提供的字段
  const { name, email } = req.body;
  if (name) user.name = name;
  if (email) {
    // 检查邮箱是否被其他人使用
    if (users.some(u => u.email === email && u.id !== id)) {
      return res.status(409).json({
        error: { code: 'CONFLICT', message: '邮箱已被使用' }
      });
    }
    user.email = email;
  }
  user.updatedAt = new Date().toISOString();
  
  res.json(user);
});
 
// 删除用户
app.delete('/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const index = users.findIndex(u => u.id === id);
  
  if (index === -1) {
    return res.status(404).json({
      error: { code: 'NOT_FOUND', message: '用户不存在' }
    });
  }
  
  users.splice(index, 1);
  res.status(204).send();
});
 
// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({
    error: {
      code: 'INTERNAL_ERROR',
      message: '服务器内部错误',
      requestId: req.id
    }
  });
});
 
app.listen(3000, () => console.log('API running on port 3000'));

6.3 NestJS 实现示例 #

// NestJS RESTful API 示例
 
// users.controller.ts
import { Controller, Get, Post, Put, Patch, Delete, Body, Param, Query, HttpCode, HttpStatus } from '@nestjs/common';
 
@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
 
  // GET /users
  @Get()
  findAll(@Query() query: PaginationQuery) {
    return this.usersService.findAll(query);
  }
 
  // GET /users/:id
  @Get(':id')
  findOne(@Param('id') id: string) {
    const user = this.usersService.findOne(+id);
    if (!user) {
      throw new NotFoundException('用户不存在');
    }
    return user;
  }
 
  // POST /users
  @Post()
  @HttpCode(HttpStatus.CREATED)
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }
 
  // PUT /users/:id
  @Put(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return this.usersService.update(+id, updateUserDto);
  }
 
  // PATCH /users/:id
  @Patch(':id')
  partialUpdate(@Param('id') id: string, @Body() partialUpdateDto: PartialUpdateUserDto) {
    return this.usersService.partialUpdate(+id, partialUpdateDto);
  }
 
  // DELETE /users/:id
  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  remove(@Param('id') id: string) {
    this.usersService.remove(+id);
  }
}
 
// users.dto.ts
import { IsString, IsEmail, IsOptional, IsInt, Min } from 'class-validator';
 
export class CreateUserDto {
  @IsString()
  name: string;
 
  @IsEmail()
  email: string;
}
 
export class UpdateUserDto {
  @IsString()
  name: string;
 
  @IsEmail()
  email: string;
}
 
export class PartialUpdateUserDto {
  @IsString()
  @IsOptional()
  name?: string;
 
  @IsEmail()
  @IsOptional()
  email?: string;
}
 
export class PaginationQuery {
  @IsInt()
  @Min(1)
  @IsOptional()
  page?: number = 1;
 
  @IsInt()
  @Min(1)
  @IsOptional()
  limit?: number = 10;
 
  @IsString()
  @IsOptional()
  sort?: string;
}

6.4 前端调用示例 #

// 前端调用 RESTful API 示例
 
// 使用 fetch
class UserAPI {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
  }
 
  // 获取所有用户
  async getAllUsers(params = {}) {
    const query = new URLSearchParams(params).toString();
    const url = `${this.baseUrl}/users${query ? '?' + query : ''}`;
    
    const response = await fetch(url);
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error.message);
    }
    
    return response.json();
  }
 
  // 获取单个用户
  async getUser(id) {
    const response = await fetch(`${this.baseUrl}/users/${id}`);
    
    if (response.status === 404) {
      throw new Error('用户不存在');
    }
    
    if (!response.ok) {
      throw new Error('请求失败');
    }
    
    return response.json();
  }
 
  // 创建用户
  async createUser(data) {
    const response = await fetch(`${this.baseUrl}/users`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    
    if (response.status === 400) {
      const error = await response.json();
      throw new Error(error.error.details.map(d => d.message).join(', '));
    }
    
    if (response.status === 409) {
      throw new Error('邮箱已被注册');
    }
    
    return response.json();
  }
 
  // 更新用户(PUT)
  async updateUser(id, data) {
    const response = await fetch(`${this.baseUrl}/users/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error.message);
    }
    
    return response.json();
  }
 
  // 部分更新(PATCH)
  async patchUser(id, data) {
    const response = await fetch(`${this.baseUrl}/users/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error.message);
    }
    
    return response.json();
  }
 
  // 删除用户
  async deleteUser(id) {
    const response = await fetch(`${this.baseUrl}/users/${id}`, {
      method: 'DELETE'
    });
    
    if (response.status === 404) {
      throw new Error('用户不存在');
    }
    
    return response.status === 204;  // true 表示删除成功
  }
}
 
// 使用示例
const api = new UserAPI('https://api.example.com/v1');
 
// 获取用户列表
const result = await api.getAllUsers({ page: 1, limit: 10 });
console.log(result.data);
 
// 创建用户
try {
  const newUser = await api.createUser({
    name: '王五',
    email: 'wang@example.com'
  });
  console.log('创建成功:', newUser);
} catch (error) {
  console.error('创建失败:', error.message);
}
 
// 更新用户邮箱
await api.patchUser(1, { email: 'new@example.com' });
 
// 删除用户
await api.deleteUser(1);

七、认证与安全 #

7.1 认证方式 #

RESTful API 常用的认证方式:

方式 说明 特点
Basic Auth 用户名密码编码 简单但不安全
API Key 固定密钥 适合公开 API
JWT 令牌认证 无状态,推荐 ✅
OAuth 2.0 第三方授权 复杂,适合社交登录

7.2 JWT 认证示例 #

# 登录获取 token
POST /auth/login
{
  "email": "user@example.com",
  "password": "password123"
}
 
# 响应
HTTP/1.1 200 OK
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600
}
 
# 使用 token 访问 API
GET /users/profile
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
 
# token 无效时
HTTP/1.1 401 Unauthorized
{
  "error": "Token 无效或已过期"
}
// Express JWT 中间件
const jwt = require('jsonwebtoken');
 
// 认证中间件
function authMiddleware(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      error: { code: 'UNAUTHORIZED', message: '请先登录' }
    });
  }
  
  const token = authHeader.slice(7);
  
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET);
    req.user = payload;  // 把用户信息放到 req
    next();
  } catch (error) {
    return res.status(401).json({
      error: { code: 'UNAUTHORIZED', message: 'Token 无效或已过期' }
    });
  }
}
 
// 使用
app.get('/users/profile', authMiddleware, (req, res) => {
  res.json(req.user);
});
 
app.delete('/users/:id', authMiddleware, (req, res) => {
  // 只有管理员或本人可以删除
  if (req.user.role !== 'admin' && req.user.id !== parseInt(req.params.id)) {
    return res.status(403).json({
      error: { code: 'FORBIDDEN', message: '无权删除该用户' }
    });
  }
  
  // 执行删除...
});

7.3 安全最佳实践 #

// 1. 输入验证
function validateEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}
 
app.post('/users', (req, res) => {
  const { email } = req.body;
  
  if (!validateEmail(email)) {
    return res.status(400).json({
      error: { message: '邮箱格式不正确' }
    });
  }
  
  // ...继续处理
});
 
// 2. 参数清理(防止注入)
const { body, validationResult } = require('express-validator');
 
app.post('/users',
  body('name').trim().escape(),  // 清理 HTML 标签
  body('email').isEmail().normalizeEmail(),
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    // ...
  }
);
 
// 3. 速率限制
const rateLimit = require('express-rate-limit');
 
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 分钟
  max: 100,  // 每个 IP 最多 100 请求
  message: {
    error: { code: 'RATE_LIMIT', message: '请求过于频繁' }
  }
});
 
app.use('/api', limiter);
 
// 4. HTTPS(生产环境必须)
// 使用 HTTPS 确保传输安全
 
// 5. CORS 配置
const cors = require('cors');
 
app.use(cors({
  origin: ['https://myapp.com'],  // 只允许特定域名
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

八、常见问题 #

Q1: RESTful API 一定要返回 JSON 吗? #

不一定。REST 不限制格式,但 JSON 是最常用的。

# 可以返回其他格式
GET /users.xml
Content-Type: application/xml
 
GET /users.csv
Content-Type: text/csv
 
# 通过 Accept 头协商(推荐)
GET /users
Accept: application/json  # 返回 JSON
 
GET /users
Accept: application/xml   # 返回 XML

Q2: 批量操作怎么做? #

# 批量删除
DELETE /users?ids=1,2,3
 
# 批量创建
POST /users/batch
[
  { "name": "用户1" },
  { "name": "用户2" },
  { "name": "用户3" }
]
 
# 批量更新
PATCH /users/batch
[
  { "id": 1, "name": "新名字1" },
  { "id": 2, "name": "新名字2" }
]

Q3: 复杂查询怎么设计? #

# 多条件组合
GET /products?
  category=electronics&
  price_min=100&
  price_max=500&
  brand=Apple&
  sort=-price&
  page=1&
  limit=20
 
# 搜索
GET /search?q=手机&type=products
 
# 聚合统计
GET /users/stats?by=role  # 按角色统计人数

Q4: 文件上传怎么做? #

# 上传单文件
POST /users/1/avatar
Content-Type: multipart/form-data
 
file: [二进制数据]
 
# 响应
HTTP/1.1 201 Created
{
  "url": "https://cdn.example.com/avatars/abc123.jpg"
}
 
# 上传多文件
POST /users/1/documents
Content-Type: multipart/form-data
 
files: [多个文件]
 
# 响应
HTTP/1.1 201 Created
{
  "documents": [
    { "id": 1, "url": "..." },
    { "id": 2, "url": "..." }
  ]
}

Q5: API 版本怎么管理? #

# 方式1:URL 路径版本(推荐)
GET /v1/users
GET /v2/users
 
# 方式2:查询参数
GET /users?version=1
 
# 方式3:Header
GET /users
Accept-Version: 1
 
# 版本升级时:
# v1 保持兼容,v2 提供新功能
# 最终废弃 v1,返回提示
GET /v1/users
HTTP/1.1 410 Gone
{
  "error": "该版本已废弃,请使用 v2",
  "newVersion": "/v2/users"
}

九、最佳实践总结 #

URL 设计检查清单 #

规则 正确示例 错误示例
使用名词 /users /getUsers
使用复数 /users /user
小写字母 /users /Users
连字符分隔 /user-profiles /userProfiles
路径参数 /users/1 /users?id=1
层级嵌套 /users/1/orders /orders?userId=1

HTTP 方法使用规则 #

方法 操作 幂等 安全 成功状态码
GET 读取 200
POST 创建 201
PUT 完整更新 200/204
PATCH 部分更新 ❌* 200
DELETE 删除 204

响应设计原则 #

// ✅ 良好的响应设计
{
  "data": { ... },           // 数据
  "pagination": { ... },     // 分页信息(列表)
  "error": { ... },          // 错误信息(失败时)
  "requestId": "abc123",     // 请求追踪
  "timestamp": "...",        // 时间戳
  "_links": { ... }          // 相关链接(可选)
}
 
// ❌ 不良设计
{
  "result": true,            // 用 status code 表示
  "msg": "成功",             // 用 status code 表示
  "data": { ... }
}

错误处理原则 #

  1. 使用正确的 HTTP 状态码
  2. 返回结构化的错误信息
  3. 包含错误代码便于调试
  4. 提供修复建议(如:参数验证错误)
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "参数验证失败",
    "details": [
      { "field": "email", "message": "邮箱格式不正确", "value": "invalid-email" }
    ]
  },
  "requestId": "req-abc123",
  "documentation": "https://docs.example.com/errors#VALIDATION_ERROR"
}

十、总结速记 #

RESTful API 核心原则 #

原则 说明
资源为中心 URL 表示资源,HTTP 方法表示操作
统一接口 GET/POST/PUT/PATCH/DELETE 标准用法
无状态 每个请求独立,不依赖 session
可缓存 GET 响应可缓存
分层系统 客户端不知道服务端结构

一句话总结 #

RESTful API = 资源(名词 URL) + 操作(HTTP 方法)

GET    /users      → 获取用户列表
POST   /users      → 创建用户
GET    /users/1    → 获取单个用户
PUT    /users/1    → 完整更新用户
PATCH  /users/1    → 部分更新用户
DELETE /users/1    → 删除用户

附录:RESTful API 设计模板 #

# RESTful API 设计模板
 
# 基本信息
API 名称: [资源名称] API
版本: v1
基础 URL: https://api.example.com/v1
 
# 资源定义
资源: [resources]
属性:
  - id: 资源唯一标识
  - [其他属性]
 
# API 操作
 
## 列表
GET /[resources]
参数:
  - page: 页码
  - limit: 每页数量
  - sort: 排序
  - [filter]: 过滤条件
响应: 200 + 列表 + 分页信息
 
## 详情
GET /[resources]/{id}
响应: 200 + 资源详情
错误: 404 (不存在)
 
## 创建
POST /[resources]
请求体: { 资源属性 }
响应: 201 + Location + 新资源
错误: 400 (参数错误), 409 (冲突)
 
## 更新
PUT /[resources]/{id}
请求体: { 完整属性 }
响应: 200
错误: 400, 404
 
## 部分更新
PATCH /[resources]/{id}
请求体: { 更新属性 }
响应: 200
错误: 400, 404
 
## 删除
DELETE /[resources]/{id}
响应: 204
错误: 404
 
# 子资源
GET /[resources]/{id}/[sub-resources]
POST /[resources]/{id}/[sub-resources]
...
 
# 认证
方式: JWT
Header: Authorization: Bearer {token}
 
# 错误格式
{
  "error": {
    "code": "ERROR_CODE",
    "message": "错误描述",
    "details": [...]
  }
}

最后更新:2026-04-01