RESTful API 教程
RESTful API 是基于 REST(Representational State Transfer,表现层状态转移)架构风格设计的 Web 接口。它用 HTTP 协议的语义来管理资源,是目前前后端分离架构中最主流的接口设计方式。
REST 的核心思想
- 资源:一切皆资源(用户、文章、订单……),用 URL 标识
- 动词:用 HTTP 方法表达操作(GET 查 / POST 增 / PUT 改 / DELETE 删)
- 表现:资源以 JSON/XML 等形式呈现给客户端
- 无状态:每个请求自包含,服务器不保存客户端状态
一个 RESTful 接口示例
| 方法 | URL | 操作 |
|---|---|---|
| GET | /api/users | 获取用户列表 |
| GET | /api/users/1 | 获取 id=1 的用户 |
| POST | /api/users | 创建用户 |
| PUT | /api/users/1 | 整体更新用户 |
| PATCH | /api/users/1 | 部分更新用户 |
| DELETE | /api/users/1 | 删除用户 |
为什么用 REST
| 优势 | 说明 |
|---|---|
| 简单直观 | URL + HTTP 方法即可表达全部操作 |
| 无状态 | 易于水平扩展、缓存 |
| 标准统一 | 前后端、多端(Web/App)共用一套接口 |
| 可缓存 | 天然适配 HTTP 缓存机制 |
RESTful API 全流程
- 设计 URL 与资源结构
- 实现各 HTTP 方法的处理逻辑
- 返回规范的 JSON 响应与正确的状态码
- 添加认证、限流、日志等保障
REST 是一种风格而非强制标准,实际项目中可结合业务灵活应用;用 FastAPI、Flask、Express 等框架都能快速搭建。
RESTful API 概念
本节深入讲解 REST 的核心概念:资源、表示、状态转移,以及 REST 与 HTTP 的关系。
什么是 REST
REST(Representational State Transfer)由 Roy Fielding 在 2000 年博士论文中提出。它的核心是:把服务器上的数据抽象为"资源",通过统一的接口(HTTP 方法)操作资源。
五大关键概念
| 概念 | 说明 | 示例 |
|---|---|---|
| 资源 | 可被识别的实体 | 用户、文章、评论 |
| 资源标识 | 用 URL 唯一标识资源 | /api/users/42 |
| 表现层 | 资源的呈现形式 | JSON / XML |
| 状态转移 | 客户端操作引起资源状态变化 | 创建 → 资源从无到有 |
| 统一接口 | 有限、固定的操作集合 | GET/POST/PUT/DELETE |
资源的命名规范
# 推荐:名词复数、层级清晰
GET /api/users # 用户集合
GET /api/users/42 # 单个用户
GET /api/users/42/posts # 该用户的文章
GET /api/posts/7/comments # 文章的评论
# 避免:动词出现在 URL(应交给 HTTP 方法)
# ❌ GET /api/getUser?id=42
# ✅ GET /api/users/42
REST 与 HTTP 的关系
REST 是架构风格,HTTP 是实现载体。REST 主张充分发挥 HTTP 协议已有能力:
- 用
GET/POST/PUT/PATCH/DELETE表达操作语义 - 用
200/201/404/500等状态码表达结果 - 用
Content-Type表达数据格式 - 用
Cache-Control控制缓存
无状态性
服务器不保存客户端会话状态,每个请求必须自包含全部信息(认证信息通过 Token 等传递)。好处是可水平扩展、故障恢复简单;代价是认证信息需重复携带。
判断一个接口是否"RESTful":URL 是否表示资源、方法是否表达语义、状态码是否准确、响应格式是否统一。
HTTP 方法
RESTful API 用 HTTP 方法表达对资源的操作,相当于一套"动词表"。选对方法,接口语义就清晰了一半。
核心方法速查
| 方法 | 语义 | REST 用途 | 幂等 |
|---|---|---|---|
GET | 读取 | 获取资源(列表/详情) | ✅ |
POST | 创建 | 新建资源 | ❌ |
PUT | 整体替换 | 整体更新资源 | ✅ |
PATCH | 局部修改 | 更新部分字段 | ❌ |
DELETE | 删除 | 删除资源 | ✅ |
GET:查询资源
# 获取所有用户
curl https://api.example.com/api/users
# 获取单个用户
curl https://api.example.com/api/users/42
# 带查询参数(过滤/分页/排序)
curl "https://api.example.com/api/users?page=2&limit=20&sort=created_at"
POST:创建资源
curl -X POST https://api.example.com/api/users -H "Content-Type: application/json" -d '{"name": "张三", "email": "zhangsan@example.com"}'
# 成功返回 201 Created + 新资源
PUT vs PATCH
# PUT:整体更新(缺省字段会被重置)
curl -X PUT https://api.example.com/api/users/42 -H "Content-Type: application/json" -d '{"name": "张三", "email": "new@example.com"}'
# PATCH:只更新指定字段
curl -X PATCH https://api.example.com/api/users/42 -H "Content-Type: application/json" -d '{"email": "new@example.com"}'
DELETE:删除资源
curl -X DELETE https://api.example.com/api/users/42
# 成功通常返回 204 No Content
GET 之外的幂等性说明
幂等:同一操作执行多次,结果一致。GET/PUT/DELETE 幂等(可安全重试);POST 不幂等(重复 POST 会创建多条资源),前端应防止重复提交。
不要用 GET 做删除、用 POST 做查询等"语义错位"——这会破坏 REST 的清晰性,也让缓存、日志分析变得混乱。
RESTful URL
RESTful URL 是资源的"地址",好的 URL 设计让接口一目了然。本节总结业界通用的 URL 设计规范。
设计原则
- 名词复数:资源名用复数名词,如
/users - 动词交给 HTTP 方法:URL 中不出现动词
- 层级表达从属:嵌套 URL 表达资源关系
- 小写 + 连字符:
/order-items而非/OrderItems - ID 用路径参数:
/users/42而非/users?id=42
URL 设计对照
| ✅ 推荐 | ❌ 避免 | 原因 |
|---|---|---|
/users | /getAllUsers | 动词冗余 |
/users/42 | /user?id=42 | 资源标识应在路径 |
/users/42/posts | /getUserPosts?uid=42 | 层级不清晰 |
/order-items | /orderItems | 规范用连字符 |
/api/v1/users | /users_v1 | 版本放前缀 |
嵌套资源
GET /api/users/42/posts # 用户 42 的文章列表
GET /api/users/42/posts/7 # 用户 42 的 id=7 的文章
POST /api/users/42/posts # 为用户 42 创建文章
嵌套不宜过深(一般不超过 2-3 层),太深时可扁平化:GET /api/posts?author_id=42
查询参数(过滤/分页/排序)
GET /api/users?status=active # 过滤
GET /api/users?page=2&page_size=20 # 分页
GET /api/users?sort=-created_at # 排序(- 表示倒序)
GET /api/users?fields=id,name,email # 字段选择
版本控制
接口演进需要版本管理,常见两种方式:
- URL 前缀(推荐):
/api/v1/users、/api/v2/users - 请求头:
Accept: application/vnd.example.v2+json
API 前缀与域名
https://api.example.com/v1/users # 独立子域名(推荐)
https://example.com/api/v1/users # 同域加 /api 前缀
URL 设计没有唯一答案,关键是全团队一致并写进文档;用 OpenAPI/Swagger 自动生成文档是常见做法。
RESTful API 请求和响应
RESTful API 的请求与响应有约定俗成的规范,遵循它们能让接口更易用、更健壮。
请求规范
| 项 | 规范 |
|---|---|
| 请求头 | Content-Type 声明体格式;Authorization 传 Token |
| 查询参数 | 过滤、分页、排序 |
| 请求体 | JSON(application/json) |
| 字符编码 | UTF-8 |
curl -X POST https://api.example.com/api/users -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOi..." -d '{"name": "张三"}'
响应状态码选择
| 场景 | 状态码 |
|---|---|
| 查询成功 | 200 OK |
| 创建成功 | 201 Created(Location 头指向新资源) |
| 删除/无内容成功 | 204 No Content |
| 请求格式错误 | 400 Bad Request |
| 未认证 | 401 Unauthorized |
| 无权限 | 403 Forbidden |
| 资源不存在 | 404 Not Found |
| 数据冲突 | 409 Conflict |
| 服务器异常 | 500 Internal Server Error |
统一响应格式
推荐封装统一结构,前端便于统一处理:
// 成功
{
"code": 0,
"message": "ok",
"data": { "id": 42, "name": "张三" }
}
// 失败
{
"code": 40400,
"message": "用户不存在",
"data": null
}
分页响应
{
"code": 0,
"data": {
"items": [ { "id": 1 }, { "id": 2 } ],
"pagination": {
"page": 1,
"page_size": 20,
"total": 105,
"total_pages": 6
}
}
}
错误响应细节
{
"code": 40000,
"message": "参数校验失败",
"errors": [
{ "field": "email", "message": "邮箱格式不正确" }
]
}
完整示例(FastAPI)
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class UserIn(BaseModel):
name: str
email: str
users = {}
@app.post("/api/users", status_code=201)
def create_user(user: UserIn):
uid = len(users) + 1
users[uid] = {"id": uid, **user.model_dump()}
return {"code": 0, "message": "ok", "data": users[uid]}
@app.get("/api/users/{uid}")
def get_user(uid: int):
if uid not in users:
raise HTTPException(status_code=404, detail="用户不存在")
return {"code": 0, "message": "ok", "data": users[uid]}
错误响应不要把堆栈信息暴露给客户端(信息泄露风险),日志里记录即可。
RESTful API 实际应用
本节通过一个完整的项目实例,展示 RESTful API 从设计到实现的真实流程:一个简单的"文章管理"接口。
1. 资源与接口设计
| 方法 | URL | 说明 |
|---|---|---|
| GET | /api/posts | 文章列表(分页) |
| GET | /api/posts/{id} | 文章详情 |
| POST | /api/posts | 创建文章 |
| PUT | /api/posts/{id} | 更新文章 |
| DELETE | /api/posts/{id} | 删除文章 |
| GET | /api/posts/{id}/comments | 文章评论 |
2. 实现(Python FastAPI)
from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel
app = FastAPI(title="博客 API")
class PostIn(BaseModel):
title: str
content: str
tags: list[str] = []
# 内存存储(演示用,生产换数据库)
db = {}
_next_id = 1
@app.get("/api/posts")
def list_posts(page: int = Query(1, ge=1), page_size: int = Query(10, ge=1, le=100)):
items = list(db.values())
start = (page - 1) * page_size
return {
"code": 0,
"data": {
"items": items[start:start + page_size],
"pagination": {"page": page, "page_size": page_size, "total": len(items)},
},
}
@app.get("/api/posts/{pid}", response_model=dict)
def get_post(pid: int):
if pid not in db:
raise HTTPException(status_code=404, detail="文章不存在")
return {"code": 0, "data": db[pid]}
@app.post("/api/posts", status_code=201)
def create_post(post: PostIn):
global _next_id
item = {"id": _next_id, **post.model_dump()}
db[_next_id] = item
_next_id += 1
return {"code": 0, "data": item}
@app.put("/api/posts/{pid}")
def update_post(pid: int, post: PostIn):
if pid not in db:
raise HTTPException(status_code=404, detail="文章不存在")
db[pid] = {"id": pid, **post.model_dump()}
return {"code": 0, "data": db[pid]}
@app.delete("/api/posts/{pid}", status_code=204)
def delete_post(pid: int):
if pid not in db:
raise HTTPException(status_code=404, detail="文章不存在")
del db[pid]
3. 调用测试
# 创建
curl -X POST http://localhost:8000/api/posts -H "Content-Type: application/json" -d '{"title": "REST 入门", "content": "..."}'
# 列表(带分页)
curl "http://localhost:8000/api/posts?page=1&page_size=10"
# 详情 / 更新 / 删除
curl http://localhost:8000/api/posts/1
curl -X PUT http://localhost:8000/api/posts/1 -H "Content-Type: application/json" -d '{"title":"新标题","content":"..."}'
curl -X DELETE http://localhost:8000/api/posts/1
4. 前端调用
async function fetchPosts(page = 1) {
const res = await fetch(`/api/posts?page=${page}&page_size=10`);
const json = await res.json();
if (json.code === 0) {
renderPosts(json.data.items);
}
}
async function createPost(data) {
const res = await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
return res.json();
}
FastAPI 会自动生成 Swagger 交互文档(/docs),可以直接在浏览器里测试所有接口,强烈推荐。
RESTful API 实践
本节聚焦 RESTful API 的最佳实践:认证、安全、版本管理与文档化,让接口达到生产级标准。
1. 认证与授权
| 方案 | 适用 | 说明 |
|---|---|---|
| JWT Token | 前后端分离 | 无状态,客户端存储,标准做法 |
| Session + Cookie | 传统 Web | 服务端存储会话 |
| API Key | 服务间/第三方 | 简单,需配合白名单 |
| OAuth2 | 第三方登录 | 标准授权协议(GitHub/微信登录) |
# FastAPI 示例:Bearer Token 依赖
from fastapi import Depends, Header, HTTPException
def verify_token(authorization: str = Header(default="")):
token = authorization.removeprefix("Bearer ")
if token != "your-secret-token": # 生产用 JWT 验证
raise HTTPException(status_code=401, detail="未认证")
return True
@app.get("/api/me")
def get_me(auth=Depends(verify_token)):
return {"code": 0, "data": {"name": "当前用户"}}
2. 安全要点
- HTTPS:所有生产接口必须 HTTPS
- 限流:防止暴力破解和滥用(429)
- 输入校验:服务端校验所有参数(不信任前端)
- 最小暴露:响应不返回 password 等敏感字段
- CORS:只允许可信来源跨域
- 日志脱敏:不记录密码、完整 Token
3. 版本管理
/api/v1/posts ← 旧版
/api/v2/posts ← 新版(breaking change 时开新版)
兼容策略:新旧版本并存一段时间,给客户端迁移窗口;文档标明废弃时间。
4. 接口文档化
# FastAPI 内置 OpenAPI 文档
# 启动后访问:
# http://localhost:8000/docs # Swagger UI
# http://localhost:8000/redoc # ReDoc
或使用 Postman / Apifox 管理接口集合与协作测试。
5. 错误处理与日志
@app.exception_handler(Exception)
async def global_error(request, exc):
logger.exception("unhandled error") # 完整堆栈进日志
return JSONResponse(status_code=500, content={"code": 50000, "message": "服务器内部错误"})
6. 测试与监控
- 单元测试:pytest + TestClient(FastAPI 自带)
- 接口测试:Postman/Apifox 集合,CI 自动跑
- 监控:响应时间、错误率、状态码分布(Prometheus + Grafana)
认证中间件要在路由层之前生效,否则静态资源或文档接口也会被拦截,注意按需放行。
RESTful API 调试
接口开发完成后,调试是必经环节。本节介绍 RESTful API 调试的常用工具与技巧。
1. curl 命令行
# GET
curl -v https://api.example.com/api/users # -v 显示完整请求/响应
# POST JSON
curl -X POST https://api.example.com/api/users -H "Content-Type: application/json" -d '{"name": "张三"}'
# 带 Token
curl -H "Authorization: Bearer TOKEN" https://api.example.com/api/me
# 只看响应头
curl -I https://api.example.com/api/users
# 保存响应到文件
curl -o resp.json https://api.example.com/api/users
2. 浏览器开发者工具
F12→ Network 面板- 发起请求后,查看 Headers(请求/响应头)、Payload(请求体)、Preview/Response(响应数据)
- 可用
filter: fetch或状态码筛选快速定位接口
3. Postman / Apifox
图形化接口调试工具,支持:
- 环境变量(dev/prod 切换)
- 集合管理(按模块组织接口)
- 自动化测试(断言状态码、字段)
- 生成代码片段(Python/JS/Go 等)
- 团队协作与文档共享
4. 常见调试问题速查
| 现象 | 排查方向 |
|---|---|
| 404 | URL 拼写、资源 ID 是否正确、路由是否注册 |
| 401/403 | Token 是否携带/过期、权限是否足够 |
| 400 | 请求体格式、必填字段、类型是否符合 |
| 500 | 看服务端日志(堆栈);数据库是否正常 |
| CORS 报错 | 服务端 Access-Control-Allow-Origin 配置 |
| 超时 | 网络、服务响应慢、限流排队 |
5. 日志辅助
import logging
logging.basicConfig(level=logging.INFO)
@app.middleware("http")
async def log_requests(request, call_next):
logger.info(f"{request.method} {request.url.path}")
response = await call_next(request)
logger.info(f"→ {response.status_code}")
return response
6. 用好 FastAPI /docs
FastAPI 的 Swagger 文档不仅展示接口,还能直接在线调试:填写参数 → Execute → 查看响应,无需额外工具。
调试 500 错误时,先看服务端日志而不是猜——堆栈会直接告诉问题所在。
RESTful API 进阶
掌握 RESTful API 的进阶主题,让接口在高并发、复杂业务下依然稳定高效。
1. 分页策略
| 方式 | 实现 | 适用 |
|---|---|---|
| 页码分页 | ?page=2&page_size=20 | 中小数据量(最常见) |
| 游标分页 | ?cursor=xxx&limit=20 | 大数据量、实时数据(翻页时数据变动安全) |
| 键集分页 | ?after_id=100 | 按主键递增的海量数据 |
# 游标分页示例
@app.get("/api/posts")
def list_posts(cursor: str | None = None, limit: int = 20):
q = db.query(Post)
if cursor:
q = q.filter(Post.id < int(cursor)) # 按 id 游标
items = q.order_by(Post.id.desc()).limit(limit).all()
next_cursor = items[-1].id if len(items) == limit else None
return {"items": items, "next_cursor": next_cursor}
2. 字段选择与稀疏字段
# 只返回需要的字段,减少传输
GET /api/users/42?fields=id,name,avatar
3. 批量操作与并发控制
POST /api/users/batch # 批量创建
PATCH /api/users/42 # 条件更新:If-Match 头 + ETag 防并发覆盖
4. 异步任务
耗时操作(发邮件、生成报表)不应阻塞请求,返回任务 ID,客户端轮询/回调:
// 提交任务
POST /api/reports → 202 Accepted
{ "task_id": "t_123", "status": "processing" }
// 查询进度
GET /api/reports/t_123 → { "status": "done", "url": "/files/report.pdf" }
5. 幂等性设计
对不幂等的 POST,可支持 Idempotency-Key 请求头:客户端每次提交带唯一键,服务端记录已处理的键,重复请求直接返回首次结果,防止重复扣款/重复下单。
6. 缓存策略
| 响应头 | 效果 |
|---|---|
Cache-Control: max-age=60 | 浏览器缓存 60 秒 |
ETag | 条件请求,304 省带宽 |
| 服务端缓存(Redis) | 热点数据快速返回 |
7. 错误码体系
{
"code": 40001,
"message": "参数错误:email 格式不正确",
"request_id": "a1b2c3" // 便于日志追踪
}
建议错误码按模块划分:4 位数字,前两位业务模块,后两位具体错误,前端可按 code 精准处理。
进阶方向:GraphQL(按需查询)、gRPC(高性能内部服务)、WebSocket(实时推送)——按场景选型,REST 仍是通用默认。
RESTful API 测验
本章测验帮助你检验对 RESTful API 的掌握程度。先独立思考,再看答案。
选择题
1. 以下哪个是获取用户 42 详情的正确 RESTful 写法?
- A.
GET /api/getUser?id=42 - B.
POST /api/users/42 - C.
GET /api/users/42✅ - D.
GET /api/user/42
解析:资源用复数名词,ID 放路径,查询用 GET。
2. 创建资源成功后,最合适的响应状态码是?
- A.
200 OK - B.
201 Created✅ - C.
204 No Content - D.
302 Found
解析:201 Created 表示资源创建成功,且响应中可带新资源。
3. 下列哪个方法不具备幂等性?
- A. GET
- B. PUT
- C. POST ✅
- D. DELETE
解析:POST 每次执行都会创建新资源,重复执行结果不同。
4. 未登录访问需要认证的接口,应返回?
- A.
403 Forbidden - B.
401 Unauthorized✅ - C.
404 Not Found - D.
500
解析:401=未认证;403=已认证但无权限。
5. "部分更新用户邮箱"应使用哪个方法?
- A. POST
- B. PUT
- C. PATCH ✅
- D. DELETE
解析:PATCH 用于局部更新,PUT 用于整体替换。
判断题
6. RESTful URL 中可以出现动词。
❌ 错误。动词应由 HTTP 方法表达(GET/POST/PUT/DELETE)。
7. REST 是无状态的。
✅ 正确。每个请求自包含,服务器不保存客户端会话状态。
8. GET 请求可以用于删除数据。
❌ 错误。GET 是只读语义,删除必须用 DELETE。
简答题
9. 简述 GET 与 POST 在 RESTful 设计中的区别。
GET 用于获取资源,参数在 URL,幂等、可缓存、无副作用;POST 用于创建资源,参数在请求体,非幂等、有副作用。两者语义不可混用。
10. 设计一个"文章评论"资源的 RESTful 接口。
GET /api/posts/{id}/comments # 评论列表
GET /api/comments/{cid} # 单条评论
POST /api/posts/{id}/comments # 发表评论
DELETE /api/comments/{cid} # 删除评论
计分建议
| 答对题数 | 水平 | 建议 |
|---|---|---|
| 10 | 优秀 | 可以开始设计生产级 API |
| 7-9 | 良好 | 复习"HTTP 方法"与"URL 设计"两节 |
| 5-6 | 及格 | 建议重学基础章节 |
| <5 | 待加强 | 把本教程完整过一遍 |
学完理论后,动手写一个真实的 CRUD 接口(用 FastAPI 或 Flask),实践是最好的巩固。