📚 全栈开发学习系列 · 阶段二(Web 全栈)
09 HTML 基础:网页骨架与语义化标签 ✅
10 CSS 基础:样式、布局与响应式 ✅
11 JavaScript 基础:变量、DOM 与事件 ✅
12 HTTP 协议基础:请求、响应与状态码 ✅
13 FastAPI 入门:构建第一个 RESTful API(当前篇)
14 PostgreSQL 基础:数据库设计与 SQL 操作
15 认证授权:JWT、Session 与 Cookie
16 综合实战:带登录的博客系统

FastAPI 入门:构建第一个 RESTful API

难度:基础 | 理解了 HTTP 协议后,亲手搭建后端服务器,用 Python 把 HTTP 理论变成可运行的 API
读完本篇你将能:用 FastAPI 框架创建第一个可运行的 API 服务,用路径参数和查询参数接收用户输入,用 Pydantic 模型自动验证请求体数据,实现完整的 CRUD(增删改查)待办事项 API,并通过浏览器自动文档页面在线测试所有接口。
📑 本文目录
01FastAPI 简介:后端框架的快车道
02环境搭建:安装 FastAPI 与 Uvicorn
03第一个 API:从 Hello World 开始
04路径参数与查询参数
05请求体:Pydantic 数据验证
06完整 CRUD:待办事项 API
07自动文档:Swagger UI 与 ReDoc

01 FastAPI 简介:后端框架的快车道

上一篇你学会了用 curl 向服务器发请求、看响应。现在换个身份——你来当服务器。FastAPI 就是帮你快速搭建 HTTP 服务器的 Python 框架。

打个比方:API 服务像一家餐厅。顾客看菜单点菜(发送 HTTP 请求),服务员把订单送到厨房(路由分发),厨师按菜谱做菜(处理函数),做完传菜出窗口(返回 HTTP 响应)。FastAPI 就是这家餐厅的运营系统——菜单自动生成(内置 API 文档),服务员自动校验订单格式(Pydantic 数据验证),厨师可以同时处理多道菜(异步支持)。

Web 框架做什么

如果没有框架,你需要从零解析 HTTP 请求行、请求头、请求体,手动路由分发,处理异常,构造响应——每一步都要手写。Web 框架把这些重复劳动封装好,你只需定义"什么 URL 对应什么函数",框架自动完成剩下的工作。

FastAPI vs Flask vs Django

特性 FastAPI Flask Django
异步支持 原生 ASGI 异步 需 async 扩展 3.1+ 部分支持
数据验证 Pydantic 自动验证 手动或扩展 Form/Serializer
API 文档 自动生成 Swagger + ReDoc 需 Flask-RESTX 等 需 DRF + drf-spectacular
学习曲线 低(会 Python 即可) 低 高(全家桶概念多)
性能 与 Node.js / Go 相当 中等 中等

FastAPI 的核心优势是类型驱动——你用 Python 类型提示声明参数类型,框架自动完成验证、转换、文档生成三件事。写一次类型标注,三处受益。

💡 小贴士
FastAPI 基于 Starlette(ASGI 工具包)和 Pydantic(数据验证库)。Pydantic v2 的验证核心用 Rust 编写,比 v1 快 5-50 倍。你不需要单独安装它们——pip install "fastapi[standard]" 会自动装好全部依赖。

02 环境搭建:安装 FastAPI 与 Uvicorn

了解了 FastAPI 的定位,现在动手安装。和所有 Python 项目一样,先建虚拟环境,再装依赖。

创建虚拟环境

Shell
mkdir fastapi-todo && cd fastapi-todo
python3 -m venv venv
source venv/bin/activate

终端提示符前出现 (venv) 表示虚拟环境已激活。Termux 用户同样执行上述命令,Python 3.13+ 可直接使用。

安装 FastAPI

Shell
pip install "fastapi[standard]"

[standard] 后缀会额外安装 Uvicorn(ASGI 服务器)、python-multipart(表单解析)等开发常用依赖。截至 2026 年 8 月,FastAPI 最新稳定版为 0.139.x,Pydantic 为 v2。

验证安装

Shell
python3 -c "import fastapi; print(fastapi.__version__)"

预期输出:

输出
0.139.2

看到版本号即安装成功。版本号可能更高,只要以 0.1 开头都说明是 FastAPI 2.x 时代前的版本(当前尚未发布 2.0)。

03 第一个 API:从 Hello World 开始

环境就绪,写第一个 API。在项目目录下创建 main.py,输入以下 6 行代码:

main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
    return {"message": "Hello, FastAPI!"}

逐行拆解这 6 行代码做了什么:

1 from fastapi import FastAPI — 导入 FastAPI 类,它是整个应用的核心
2 app = FastAPI() — 创建应用实例,所有路由都注册到这个对象上
3 @app.get("/") — 装饰器,声明这个函数处理 GET 请求,路径是根路径 /
4 def read_root(): — 处理函数,函数名会出现在自动文档中
5 return {"message": "Hello, FastAPI!"} — 返回字典,FastAPI 自动转为 JSON 响应

启动开发服务器

FastAPI 自带 CLI 命令,在项目目录下执行:

Shell
fastapi dev

终端输出:

终端输出
1
2
3
4
5
FastAPI
Starting development server 🚀
INFO:     Application startup complete.
Uvicorn running on http://127.0.0.1:8000
(Press CTRL+C to quit)

fastapi dev 会自动找到 main.py 中的 app 变量,用 Uvicorn 启动开发服务器,并开启热重载——你修改代码保存后服务器自动重启。

也可以用传统命令:uvicorn main:app --reload。其中 main:app 表示"main.py 文件中的 app 变量",--reload 开启热重载。

测试 API

开一个新终端窗口(保持服务器运行),用上一篇学过的 curl 发请求:

Shell
curl http://127.0.0.1:8000/

响应:

响应
{"message":"Hello, FastAPI!"}

你返回的 Python 字典 {"message": "Hello, FastAPI!"} 被 FastAPI 自动转换成了 JSON 响应。响应头中 content-type 自动设为 application/json——上一篇学过的 HTTP 知识在这里派上了用场。

04 路径参数与查询参数

Hello World 只返回固定内容。真实的 API 需要接收用户传入的参数。FastAPI 从函数签名中自动提取参数——路径中的变量变成路径参数,URL 问号后的变量变成查询参数。

路径参数

在 URL 路径中用花括号 {} 声明变量,函数参数名与花括号内的名称对应:

main.py(追加)
1
2
3
4
@app.get("/users/{user_id}")
def read_user(user_id: int):
    return {"user_id": user_id}

注意 user_id: int 的类型标注。FastAPI 会自动把 URL 中的字符串转为 int,如果转换失败直接返回 422 错误:

Shell
curl http://127.0.0.1:8000/users/42
{"user_id":42}
# 传入非数字 → 自动返回 422
curl http://127.0.0.1:8000/users/abc
{"detail":[{"msg":"Input should be a valid integer"...}]}

查询参数

不在路径花括号中的函数参数,FastAPI 自动当作查询参数。用默认值声明可选参数:

main.py(追加)
1
2
3
4
@app.get("/items")
def list_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

测试不同 URL 的效果:

Shell
# 不传参数 → 用默认值
curl http://127.0.0.1:8000/items
{"skip":0,"limit":10}
# 传两个参数
curl "http://127.0.0.1:8000/items?skip=20&limit=5"
{"skip":20,"limit":5}
💡 小贴士
URL 中带 & 等特殊字符时,curl 需要用引号包裹整个 URL,否则 shell 会把 & 解释为后台运行符。Python 3.10+ 的类型标注 str | None 等价于 Optional[str],但更简洁。

05 请求体:Pydantic 数据验证

GET 请求通过 URL 传参够了,但 POST/PUT 请求需要发送结构化数据(JSON 请求体)。FastAPI 用 Pydantic 模型自动解析和验证请求体——你定义数据结构,框架负责校验。

定义 Pydantic 模型

main.py(追加)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pydantic import BaseModel, Field
class TodoCreate(BaseModel):
    # ... 表示必填,min_length/max_length 限制长度
    title: str = Field(..., min_length=1, max_length=100)
    # None 表示可选,不传时默认为 None
    description: str | None = None
    # ge=1 le=5 限制范围:大于等于1,小于等于5
    priority: int = Field(default=1, ge=1, le=5)
@app.post("/todos")
def create_todo(todo: TodoCreate):
    return {"title": todo.title, "priority": todo.priority}

Field(...) 中的 ... 表示必填(Ellipsis),default=1 表示有默认值。ge(greater than or equal)和 le(less than or equal)限定数值范围。

验证效果

发送合法数据:

Shell
curl -X POST http://127.0.0.1:8000/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"买牛奶","priority":3}'
{"title":"买牛奶","priority":3}

发送非法数据(priority 超出范围):

Shell
curl -X POST http://127.0.0.1:8000/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"测试","priority":99}'
{"detail":[{"msg":"Input should be less than or equal to 5"...}]}

FastAPI 自动返回 422 状态码和详细错误信息,告诉你哪个字段出了什么问题。不需要手写任何 if title is None 或 if priority > 5 的校验代码。

⚠️ 常见错误
curl -X POST http://127.0.0.1:8000/todos -d '{"title":"test"}' — 缺少 Content-Type 头,FastAPI 不知道请求体是 JSON,返回 422
✓ 正确:加上 -H "Content-Type: application/json" 头

06 完整 CRUD:待办事项 API

前面分块学了路径参数、查询参数、请求体验证。现在把它们组合起来,实现一个完整的待办事项(Todo)CRUD API——增删改查四件套,对应 HTTP 的 POST/DELETE/PUT/GET。

为了聚焦 FastAPI 本身,本篇用内存列表存储数据(重启后丢失)。第 14 篇会接入 PostgreSQL 数据库实现持久化。

完整代码

用以下内容替换 main.py(共约 45 行):

main.py(完整版)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI()
class TodoCreate(BaseModel):
    title: str = Field(..., min_length=1, max_length=100)
    description: str | None = None
    priority: int = Field(default=1, ge=1, le=5)
class TodoResponse(BaseModel):
    id: int
    title: str
    description: str | None
    priority: int
    completed: bool
todos: list[dict] = []
next_id = 1
@app.get("/todos", response_model=list[TodoResponse])
def list_todos():
    return todos
@app.post("/todos", response_model=TodoResponse, status_code=201)
def create_todo(todo: TodoCreate):
    global next_id
    new_todo = {"id": next_id, "title": todo.title,
        "description": todo.description, "priority": todo.priority,
        "completed": False}
    todos.append(new_todo)
    next_id += 1
    return new_todo
@app.get("/todos/{todo_id}", response_model=TodoResponse)
def get_todo(todo_id: int):
    for todo in todos:
        if todo["id"] == todo_id:
            return todo
    raise HTTPException(status_code=404, detail="待办不存在")
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, todo: TodoCreate):
    for i, item in enumerate(todos):
        if item["id"] == todo_id:
            todos[i].update({"title": todo.title,
                "description": todo.description, "priority": todo.priority})
            return todos[i]
    raise HTTPException(status_code=404, detail="待办不存在")
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int):
    for i, item in enumerate(todos):
        if item["id"] == todo_id:
            todos.pop(i)
            return
    raise HTTPException(status_code=404, detail="待办不存在")

端点与 HTTP 方法对照

方法 路径 功能 状态码
GET /todos 获取全部待办 200
POST /todos 创建新待办 201
GET /todos/{id} 获取单个待办 200 / 404
PUT /todos/{id} 更新待办内容 200 / 404
DELETE /todos/{id} 删除待办 204 / 404

端到端测试

保存代码后服务器自动重载,依次执行以下命令体验完整流程:

Shell
1
2
3
4
5
6
7
8
9
10
11
# 1. 创建待办
curl -X POST http://127.0.0.1:8000/todos \
  -H "Content-Type: application/json" \
  -d '{"title":"学FastAPI","priority":5}'
{"id":1,"title":"学FastAPI","description":null,"priority":5,"completed":false}
# 2. 查看全部
curl http://127.0.0.1:8000/todos
[{"id":1,"title":"学FastAPI","description":null,"priority":5,"completed":false}]
# 3. 删除
curl -X DELETE http://127.0.0.1:8000/todos/1 -i

DELETE 请求加 -i 可以看到状态行 HTTP/1.1 204 No Content——上一篇学过的 204 表示"成功但无返回体"。

💡 小贴士
response_model 参数控制响应体结构——即使内部返回多余的字典字段,FastAPI 也只输出 model 中声明的字段。这叫"响应过滤",防止意外泄露内部数据。TodoCreate 是输入模型(用户提交什么),TodoResponse 是输出模型(API 返回什么),分开定义让接口更安全。

07 自动文档:Swagger UI 与 ReDoc

写完 CRUD 代码,FastAPI 已经根据你的路由和 Pydantic 模型自动生成了两套交互式 API 文档——不需要任何额外配置或注解。

Swagger UI(交互式测试)

浏览器打开 http://127.0.0.1:8000/docs,你会看到所有 API 端点按方法分组排列,每个端点显示路径、HTTP 方法、请求参数结构和响应模型。

操作流程:

1 点击 POST /todos 端点,展开详情
2 点击右侧 Try it out 按钮
3 在 Request body 输入框中编辑 JSON(已有模板示例)
4 点击 Execute 发送请求
5 下方显示响应状态码、响应头和响应体

ReDoc(结构化文档)

打开 http://127.0.0.1:8000/redoc,ReDoc 提供更适合阅读的三栏式文档——左侧目录、中间详情、右侧示例。适合生成对外发布的 API 参考文档。

OpenAPI Schema

底层支撑两套文档的是 http://127.0.0.1:8000/openapi.json——一份符合 OpenAPI 规范的 JSON 描述文件。它记录了所有端点、参数、模型、响应结构。前后端团队可以基于这份 JSON 自动生成前端请求代码、Mock 服务器或其他语言的客户端 SDK。

💡 小贴士
在函数中写 docstring(三引号注释)会自动显示在文档中。"""获取全部待办事项。支持分页参数 skip 和 limit。""" 这样的描述会出现在 Swagger UI 的端点说明区域,让团队协作时接口用途一目了然。
📖 知识回顾
FastAPI() 实例 @app.get/post/put/delete 路径参数 {id} 查询参数 ?skip=0 Pydantic BaseModel Field 验证约束 422 自动验证 response_model HTTPException CRUD 五端点 /docs Swagger UI /redoc 文档 openapi.json
✏️ 动手练习
🟢 基础验证
在本篇的待办事项 API 基础上,新增一个 GET /todos/{todo_id}/toggle 端点,将指定待办的 completed 字段在 True/False 之间切换,并返回更新后的待办。用 curl 或 Swagger UI 测试。
参考方向:在 get_todo 的循环逻辑基础上,找到目标待办后执行 todo["completed"] = not todo["completed"],然后 return todo。记得加 response_model=TodoResponse。
🟡 组合应用
给待办列表 API 增加分页功能:在 GET /todos 端点添加 skip 和 limit 查询参数,返回分页后的列表。同时添加 priority 查询参数实现按优先级筛选。
参考方向:def list_todos(skip: int = 0, limit: int = 10, priority: int | None = None)。先用列表推导式按 priority 筛选,再用切片 [skip:skip+limit] 分页。
🔴 开放挑战
将待办数据从内存列表改为保存到 JSON 文件中,实现重启后数据不丢失。提示:用 Python 内置的 json 模块,在 create/update/delete 操作后调用 json.dump 写入文件,启动时调用 json.load 读取。思考:多个请求同时写入时会有什么问题?搜索关键词"文件锁 Python"了解解决方案。
下篇预告
14 PostgreSQL 基础:数据库设计与 SQL 操作
本篇的待办数据存在内存中,重启即丢失。下一步接入 PostgreSQL 数据库——学习表设计、主键与外键、CRUD 的 SQL 语句,让 FastAPI 后端拥有真正的数据持久化能力
关注公众号 · 持续获取全栈开发学习更新