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 全流程

  1. 设计 URL 与资源结构
  2. 实现各 HTTP 方法的处理逻辑
  3. 返回规范的 JSON 响应与正确的状态码
  4. 添加认证、限流、日志等保障
💡 提示

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 设计规范。

设计原则

  1. 名词复数:资源名用复数名词,如 /users
  2. 动词交给 HTTP 方法:URL 中不出现动词
  3. 层级表达从属:嵌套 URL 表达资源关系
  4. 小写 + 连字符:/order-items 而非 /OrderItems
  5. 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. 浏览器开发者工具

  1. F12 → Network 面板
  2. 发起请求后,查看 Headers(请求/响应头)、Payload(请求体)、Preview/Response(响应数据)
  3. 可用 filter: fetch 或状态码筛选快速定位接口

3. Postman / Apifox

图形化接口调试工具,支持:

  • 环境变量(dev/prod 切换)
  • 集合管理(按模块组织接口)
  • 自动化测试(断言状态码、字段)
  • 生成代码片段(Python/JS/Go 等)
  • 团队协作与文档共享

4. 常见调试问题速查

现象排查方向
404URL 拼写、资源 ID 是否正确、路由是否注册
401/403Token 是否携带/过期、权限是否足够
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),实践是最好的巩固。

第 1 / 10 节