文章
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 ContentDELETE 的特点:
- 幂等:删除一次和删除多次结果相同
- 不安全:会修改数据
- 成功返回 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=13.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=203.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, 4036.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 # 返回 XMLQ2: 批量操作怎么做? #
# 批量删除
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": { ... }
}错误处理原则 #
- 使用正确的 HTTP 状态码
- 返回结构化的错误信息
- 包含错误代码便于调试
- 提供修复建议(如:参数验证错误)
{
"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