Skip to content

第四章 自动交互式文档

本章目标:掌握 FastAPI 内置的两种 API 文档(Swagger UI / ReDoc),学会通过代码优化文档展示效果,让 API 文档更专业、更好用。

预计时长:20 分钟


4.1 Swagger UI(/docs)

启动任何 FastAPI 应用后,访问 http://127.0.0.1:8000/docs 即可打开 Swagger UI。

它能做什么

Swagger UI 功能一览:
┌─────────────────────────────────────────────┐
│  Swagger UI  (http://127.0.0.1:8000/docs)   │
├─────────────────────────────────────────────┤
│  ✅ 列出所有接口(路径 + 方法)               │
│  ✅ 展示每个接口的参数说明                    │
│  ✅ 在线填写参数,点击 Execute 直接调用       │
│  ✅ 查看真实的请求 URL、请求头、请求体        │
│  ✅ 查看响应状态码、响应体、响应头            │
│  ✅ 展示请求/响应的 JSON Schema              │
│  ✅ 支持 Bearer Token 等认证方式             │
└─────────────────────────────────────────────┘

在线测试 API

准备一个示例应用 chapter04.py

python
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI(title="第四章示例 API", version="1.0.0")


class Item(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    price: float = Field(gt=0)
    quantity: int = Field(ge=0, default=0)


items_db: dict[int, dict] = {}
next_id = 1


@app.post("/items", status_code=201)
def create_item(item: Item):
    global next_id
    new_item = {"id": next_id, **item.model_dump()}
    items_db[next_id] = new_item
    next_id += 1
    return new_item


@app.get("/items/{item_id}")
def get_item(item_id: int):
    if item_id not in items_db:
        return {"error": "Item not found"}
    return items_db[item_id]


@app.get("/items")
def list_items(skip: int = 0, limit: int = 10):
    all_items = list(items_db.values())
    return all_items[skip : skip + limit]
bash
fastapi dev chapter04.py

打开 http://127.0.0.1:8000/docs ,操作流程:

步骤操作说明
1找到 POST /items绿色标签表示 POST 方法
2点击展开 → 点击 Try it out进入编辑模式
3在 Request body 中填写 JSON{"name": "键盘", "price": 299, "quantity": 5}
4点击 Execute发起真实请求
5查看下方 Response body看到返回的 JSON(含自动分配的 id)

查看请求/响应 Schema

在 Swagger UI 页面底部有一个 Schemas 区域,展示了所有 Pydantic 模型的 JSON Schema:

Schemas 区域展示:
┌──────────────────────────────┐
│  Item                        │
│  ├── name*    string         │
│  │   minLength: 1            │
│  │   maxLength: 100          │
│  ├── price*   number         │
│  │   exclusiveMinimum: 0     │
│  └── quantity integer        │
│      minimum: 0              │
│      default: 0              │
└──────────────────────────────┘

每个接口展开后也能切换查看:

标签内容
Schema字段名、类型、约束、是否必填
Example Value自动生成的示例 JSON

这些信息全部来自你的 Pydantic 模型定义和 Field() 参数,零额外配置


4.2 ReDoc(/redoc)

访问 http://127.0.0.1:8000/redoc 即可打开 ReDoc 文档。

Swagger UI vs ReDoc

维度Swagger UI(/docs)ReDoc(/redoc)
交互性可直接发请求测试只读,不能发请求
布局单栏,逐个展开三栏,左侧导航 + 中间说明 + 右侧示例
适合谁开发者自己调试分享给前端 / 第三方 / 写文档
信息密度适中更高,一屏看到更多信息
美观度功能优先排版更专业

适合什么场景

开发阶段 → Swagger UI(/docs)
  ├── 自己调试接口
  ├── 在线修改参数测试
  └── 快速验证返回值

交付阶段 → ReDoc(/redoc)
  ├── 发给前端同事看接口定义
  ├── 发给第三方对接
  └── 作为项目 API 文档存档

提示:两个文档页面基于同一份 OpenAPI Schema(/openapi.json),内容完全一致,只是展现形式不同。


4.3 如何优化文档展示

默认文档已经很好用了,但通过几个简单的参数可以让文档更专业。

路由参数:summary、description、tags

python
from fastapi import FastAPI
from pydantic import BaseModel, Field

app = FastAPI(
    title="商品管理 API",
    description="一个简单的商品 CRUD 示例,用于演示文档优化。",
    version="2.0.0",
)


class ItemCreate(BaseModel):
    name: str = Field(min_length=1, max_length=100)
    price: float = Field(gt=0, description="商品单价(元)")
    quantity: int = Field(ge=0, default=0, description="库存数量")


class ItemResponse(BaseModel):
    id: int
    name: str
    price: float
    quantity: int


items_db: dict[int, dict] = {}
next_id = 1


@app.post(
    "/items",
    response_model=ItemResponse,
    status_code=201,
    tags=["商品管理"],
    summary="创建商品",
    description="传入商品名称、价格和库存数量,创建一条新商品记录。返回包含自动分配 ID 的完整商品信息。",
)
def create_item(item: ItemCreate):
    global next_id
    new_item = {"id": next_id, **item.model_dump()}
    items_db[next_id] = new_item
    next_id += 1
    return new_item


@app.get(
    "/items/{item_id}",
    response_model=ItemResponse,
    tags=["商品管理"],
    summary="获取单个商品",
    description="根据商品 ID 获取商品详情。",
)
def get_item(item_id: int):
    from fastapi import HTTPException
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="商品不存在")
    return items_db[item_id]


@app.get(
    "/items",
    response_model=list[ItemResponse],
    tags=["商品管理"],
    summary="获取商品列表",
    description="分页获取所有商品,支持 `skip` 和 `limit` 查询参数。",
)
def list_items(skip: int = 0, limit: int = 10):
    all_items = list(items_db.values())
    return all_items[skip : skip + limit]


@app.get(
    "/health",
    tags=["系统"],
    summary="健康检查",
)
def health_check():
    return {"status": "ok"}

各参数的作用:

参数出现位置效果
tags=["商品管理"]文档左侧分组 / Swagger UI 折叠分组接口按标签归类,一目了然
summary="创建商品"接口标题(折叠状态下可见)替代默认的函数名
description="..."接口展开后的详细说明支持 Markdown 格式
response_model=ItemResponseSchema 区域明确展示返回数据结构

效果对比:

优化前(默认):                     优化后:
┌──────────────────────┐           ┌──────────────────────┐
│ default              │           │ 商品管理              │
│   GET  /items        │           │   POST /items 创建商品│
│   POST /items        │           │   GET  /items 商品列表│
│   GET  /items/{id}   │           │   GET  /items/{id}   │
│   GET  /health       │           │                      │
└──────────────────────┘           │ 系统                  │
                                   │   GET  /health 健康检查│
                                   └──────────────────────┘

Pydantic Model 的 json_schema_extra(示例值)

默认情况下,Swagger UI 会根据字段类型自动生成示例值("string"0 等),不太直观。通过 model_config 可以提供更有意义的示例:

python
from pydantic import BaseModel, Field


class ItemCreate(BaseModel):
    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "name": "机械键盘",
                    "price": 399.0,
                    "quantity": 50,
                }
            ]
        }
    }

    name: str = Field(min_length=1, max_length=100, description="商品名称")
    price: float = Field(gt=0, description="商品单价(元)")
    quantity: int = Field(ge=0, default=0, description="库存数量")

也可以在 Field() 级别单独设置示例值:

python
class ItemCreate(BaseModel):
    name: str = Field(
        min_length=1,
        max_length=100,
        description="商品名称",
        examples=["机械键盘"],
    )
    price: float = Field(
        gt=0,
        description="商品单价(元)",
        examples=[399.0],
    )
    quantity: int = Field(
        ge=0,
        default=0,
        description="库存数量",
        examples=[50],
    )

两种方式的对比:

方式粒度适合场景
model_configjson_schema_extra整个模型级别提供一组完整的示例数据
Field(examples=[...])单个字段级别各字段独立展示示例

接口分组与排序

接口在文档中的分组由 tags 控制,排序由 tag 出现的先后顺序路由注册顺序 决定。

如果想精确控制 tag 的顺序和描述,在 FastAPI() 实例中配置 openapi_tags

python
from fastapi import FastAPI

tags_metadata = [
    {
        "name": "商品管理",
        "description": "商品的增删改查接口",
    },
    {
        "name": "订单管理",
        "description": "订单相关操作",
    },
    {
        "name": "系统",
        "description": "健康检查、版本信息等系统级接口",
    },
]

app = FastAPI(
    title="商品管理系统",
    version="2.0.0",
    openapi_tags=tags_metadata,
)

文档中 tag 的显示顺序 = openapi_tags 列表中的定义顺序,同一 tag 下的接口按路由注册顺序排列。

完整优化示例

把以上所有优化技巧整合到一个完整可运行的文件 chapter04_full.py

python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

tags_metadata = [
    {"name": "商品管理", "description": "商品的增删改查操作"},
    {"name": "系统", "description": "系统级接口"},
]

app = FastAPI(
    title="商品管理系统 API",
    description="FastAPI 文档优化示例,演示 tags / summary / description / json_schema_extra 的效果。",
    version="2.0.0",
    openapi_tags=tags_metadata,
)


# ---------- 数据模型 ----------
class ItemCreate(BaseModel):
    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "name": "机械键盘",
                    "price": 399.0,
                    "quantity": 50,
                }
            ]
        }
    }

    name: str = Field(min_length=1, max_length=100, description="商品名称")
    price: float = Field(gt=0, description="商品单价(元)")
    quantity: int = Field(ge=0, default=0, description="库存数量")


class ItemResponse(BaseModel):
    id: int = Field(description="商品 ID")
    name: str = Field(description="商品名称")
    price: float = Field(description="商品单价(元)")
    quantity: int = Field(description="库存数量")


# ---------- 模拟存储 ----------
items_db: dict[int, dict] = {}
next_id = 1


# ---------- 接口 ----------
@app.post(
    "/items",
    response_model=ItemResponse,
    status_code=201,
    tags=["商品管理"],
    summary="创建商品",
    description="传入商品信息,创建一条新记录并返回完整数据(含自动分配的 ID)。",
)
def create_item(item: ItemCreate):
    global next_id
    new_item = {"id": next_id, **item.model_dump()}
    items_db[next_id] = new_item
    next_id += 1
    return new_item


@app.get(
    "/items",
    response_model=list[ItemResponse],
    tags=["商品管理"],
    summary="获取商品列表",
    description="分页获取所有商品。`skip` 跳过前 N 条,`limit` 限制返回数量。",
)
def list_items(skip: int = 0, limit: int = 10):
    return list(items_db.values())[skip : skip + limit]


@app.get(
    "/items/{item_id}",
    response_model=ItemResponse,
    tags=["商品管理"],
    summary="获取单个商品",
    description="根据商品 ID 查询详情,不存在时返回 404。",
)
def get_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="商品不存在")
    return items_db[item_id]


@app.delete(
    "/items/{item_id}",
    tags=["商品管理"],
    summary="删除商品",
    description="根据商品 ID 删除记录,不存在时返回 404。",
)
def delete_item(item_id: int):
    if item_id not in items_db:
        raise HTTPException(status_code=404, detail="商品不存在")
    del items_db[item_id]
    return {"message": "删除成功"}


@app.get(
    "/health",
    tags=["系统"],
    summary="健康检查",
    description="返回服务运行状态,可用于负载均衡器探活。",
)
def health_check():
    return {"status": "ok", "version": "2.0.0"}
bash
fastapi dev chapter04_full.py

启动后分别打开 /docs/redoc,观察:

  1. 接口按「商品管理」「系统」分组
  2. 每个接口有清晰的中文标题和描述
  3. 请求体有真实的示例数据(机械键盘 / 399 / 50)
  4. 响应体展示了精确的 Schema

4.4 动手练习

练习 1:给第三章的 Todo API 添加文档优化

回到之前的 Todo 应用,完成以下优化:

  1. FastAPI() 添加 titledescriptionversion
  2. 给每个路由添加 tagssummarydescription
  3. TodoCreate 模型添加 json_schema_extra 示例值
  4. 定义 openapi_tags 控制分组顺序

参考效果:

python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, field_validator

tags_metadata = [
    {"name": "Todo", "description": "待办事项的增删改查"},
]

app = FastAPI(
    title="Todo API",
    description="一个支持数据校验的待办事项管理接口",
    version="1.0.0",
    openapi_tags=tags_metadata,
)


class TodoCreate(BaseModel):
    model_config = {
        "json_schema_extra": {
            "examples": [
                {
                    "title": "学习 FastAPI 文档优化",
                    "description": "掌握 tags、summary、json_schema_extra 的用法",
                    "priority": 4,
                }
            ]
        }
    }

    title: str = Field(min_length=1, max_length=200, description="待办事项标题")
    description: str | None = Field(default=None, max_length=500, description="详细说明")
    priority: int = Field(ge=1, le=5, default=3, description="优先级 1~5,数字越大越紧急")

    @field_validator("title")
    @classmethod
    def title_must_not_be_blank(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("标题不能全是空格")
        return v.strip()


class TodoResponse(BaseModel):
    id: int = Field(description="自动分配的唯一 ID")
    title: str
    description: str | None
    priority: int
    completed: bool


todos: dict[int, dict] = {}
next_id = 1


@app.post("/todos", response_model=TodoResponse, status_code=201,
          tags=["Todo"], summary="创建待办")
def create_todo(todo: TodoCreate):
    global next_id
    new_todo = {"id": next_id, "completed": False, **todo.model_dump()}
    todos[next_id] = new_todo
    next_id += 1
    return new_todo


@app.get("/todos", response_model=list[TodoResponse],
         tags=["Todo"], summary="获取待办列表")
def list_todos():
    return list(todos.values())


@app.get("/todos/{todo_id}", response_model=TodoResponse,
         tags=["Todo"], summary="获取单个待办")
def get_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="Todo 不存在")
    return todos[todo_id]

练习 2:自定义文档 URL

FastAPI 允许修改文档的访问路径,甚至关闭文档:

python
app = FastAPI(
    docs_url="/swagger",      # Swagger UI 改为 /swagger
    redoc_url="/api-doc",     # ReDoc 改为 /api-doc
)

# 生产环境关闭文档:
# app = FastAPI(docs_url=None, redoc_url=None)

试试修改后访问新路径,确认文档正常显示。


本章小结

概念要点
Swagger UI/docs,可在线测试接口,开发阶段首选
ReDoc/redoc,三栏布局,适合分享给前端/第三方
OpenAPI Schema/openapi.json,两种文档的数据源,自动生成
tags接口分组,在文档中按标签归类展示
summary接口标题,折叠时可见
description接口详细说明,支持 Markdown
json_schema_extra为 Pydantic 模型提供完整示例值
Field(examples=[...])为单个字段提供示例值
openapi_tags控制 tag 顺序和描述
docs_url / redoc_url自定义或关闭文档路径

下一章预告:我们将学习 FastAPI 最强大的特性之一——依赖注入系统。它能帮你把公共逻辑(数据库连接、用户认证、权限校验)优雅地抽离出来,让路由函数保持简洁。

坚持是一种品格