码道: 用 FastAPI 五分钟写一个学生信息管理后端:从内存存储到 Swagger 文档全流程实战
一、为什么要写这个项目
在学习 Python Web 开发的路上,很多初学者会遇到一个共同困境:看了很多教程,背了很多概念,但当真正动手写一个「看起来完整的东西」时,却常常无从下手。比如数据库还没学会、Django 太重、Flask 太原始……于是热情就在一轮轮的「看懂但写不出来」中被消磨殆尽。
这篇文章想告诉你的是:学习后端开发,不需要从数据库开始,也不需要从重量级框架开始。 用 FastAPI 这样的现代轻量框架,配合内存数据结构,5 分钟就能写出一套结构完整、文档自动生成、可以真实运行的 HTTP 接口服务。
本项目要做的事非常朴素:实现一个「学生表」的增删改查接口。数据不需要存到 MySQL、不需要 Redis,就用一个 Python 列表放在内存里。是的,就这么简单。但麻雀虽小五脏俱全——这个项目里你会接触到 RESTful 设计、请求参数校验、路径参数、查询参数、状态码约定、自动文档、单元测试、项目分层等后端开发的核心知识点。
二、技术选型:为什么是 FastAPI
在选择框架之前,我们快速回顾一下 Python 主流 Web 框架的定位:
- Flask:老牌的微框架,灵活但一切都要自己搭,写文档要单独接 flasgger 或 flask-restx,繁琐。
- Django:大而全的「全家桶」,自带 ORM、Admin、Auth,但学习曲线陡峭,杀鸡用牛刀的感觉明显。
- FastAPI:2018 年才诞生的新一代异步框架,最大的卖点是基于 Python 类型注解自动生成 OpenAPI 文档,天然拥抱类型系统,性能直逼 Node.js 和 Go。
为什么最终选择 FastAPI?除了社区火热、Star 数极高之外,最关键的理由有三个:
- 零配置出文档:写完接口后访问
/docs就是 Swagger UI,交互式调试文档直接可用,这正是本项目「自带 API 说明」的天然答案。 - 类型即校验:Pydantic 模型 + 类型注解双重校验,非法参数自动返回 422,连手写校验的代码都省了。
- 未来友好:原生支持异步、支持 WebSocket,往后是做大项目的底气。
当然,内存存储也绝不是偷懒。它让整个项目「零依赖数据库」,读者可以专心理解「路由 → 参数 → 业务逻辑 → 返回」这条主链路,然后再平滑迁移到真实数据库。
三、项目结构设计与分层思想
好的项目从组织结构开始。虽然这是一个演示级项目,但依然采用了「入口 / 模型 / 存储 / 路由」分层的经典结构:
app/
├── main.py # 应用入口:创建 FastAPI 实例、注册路由、配置中间件
├── models.py # Pydantic 数据模型:定义字段类型与校验规则
├── store.py # 数据存储层:内存列表 + 线程锁,封装 CRUD 逻辑
└── routers/
└── students.py # 学生路由:定义 6 个 HTTP 接口
tests/
└── test_students.py # 单元测试
每一层的职责是单一的、清晰的:
- 入口层
main.py只关心「组装」:把 App 创建好、把路由挂载上去、把跨域中间件配上,自己不写业务。 - 模型层
models.py只描述「数据长什么样」,例如学生有姓名、年龄、性别、班级、邮箱、电话,以及各自的约束规则。 - 存储层
store.py屏蔽了「数据放在哪、怎么存取」,对外暴露增删改查方法。将来换成数据库,只要改这一层。 - 路由层
students.py定义「URL 和 HTTP 方法怎么对应到存储操作」,并负责响应格式的约定。
这种分层的收益是巨大的:接口层不关心存储细节,存储层不关心 HTTP 细节,任何一层的替换都不会引发雪崩。很多初学者把这些逻辑全写在一个文件里,项目一大就寸步难行。分层的习惯,应当从第一个项目就开始养成。
四、数据模型:用类型说话
在 models.py 中,我们用 Pydantic 定义了两套模型:
StudentCreate:创建学生时的请求体,拥有name、age、gender、grade、email、phone字段,其中姓名必填且长度为 1~50,年龄必须在 0~150 之间,性别必须是规定的枚举值之一。StudentOut:响应用户的数据结构,额外包含系统分配的id。StudentUpdate:更新时的可选字段模型,每个字段都可缺省,且校验规则与上面一致。
这里要特别说说 Pydantic 的威力。以往在 Flask 里,我们要写大段的 if not name: raise ... 之类的校验代码;而在 FastAPI + Pydantic 下,只需声明:
age: int = Field(..., ge=0, le=150, description="学生年龄")
一行声明,就同时完成了「必须是整数」「不能小于 0」「不能大于 150」三条规则。更棒的是,性别校验通过 field_validator 自定义:
@field_validator("gender")
def validate_gender(cls, v):
if v not in ("男", "女", "男", "unknown" ...):
raise ValueError("...")
当客户端传入非法数据时,FastAPI 会自动返回:
{
"detail": [
{"loc": ["body", "age"], "msg": "Input should be less than or equal to 150", "type": "less_than_equal"}
]
}
HTTP 状态码 422,错误字段、错误原因一目了然。类型系统即文档、即校验,这就是现代 Python Web 开发的优雅之处。
五、内存存储层:一句话讲透线程安全
存储层是最能体现「小而精」的部分。我们用一个类 StudentStore 封装了:
- 内部一个
list存放全部学生对象; - 一个
_next_id计数器负责 ID 自增; - 一个
threading.Lock保证并发安全。
所有 CRUD 方法都包在 with self._lock: 中执行。为什么要加锁?FastAPI 的 Uvicorn 服务器在默认模式下是多线程处理的,多个请求可能同时修改同一个列表——比如两个请求同时新增,如果 ID 分配和列表追加不是原子的,就可能出现脏数据。加一把线程锁,就能把「检查 → 修改」这段临界区保护起来。这看似一个细节,却是真实并发编程的第一课,值得所有初学者记住:共享可变状态,必须有同步机制。
而选择列表作为存储,也让代码的可读性达到最高:
def get(self, student_id):
for student in self._students:
if student.id == student_id:
return student
return None
遍历找元素,逻辑清晰到不需要注释。当列表不再是瓶颈时(比如数据量大到需要索引),再把内部实现替换成数据库表即可,对外接口完全不变。
六、路由层:把 CRUD 翻译成 HTTP
路由层定义了六个接口,全部放入 /api/v1/students 前缀,遵循 RESTful 设计规范:
| 方法 | 路径 | 语义 | 成功状态码 |
|---|---|---|---|
| GET | /api/v1/students | 查询列表(可搜索、分页) | 200 |
| GET | /api/v1/students/{id} | 查询单个学生 | 200 |
| POST | /api/v1/students | 新增学生 | 201 |
| PUT | /api/v1/students/{id} | 更新学生 | 200 |
| DELETE | /api/v1/students/{id} | 删除学生 | 204 |
几个值得玩味的细节:
第一,查询列表做了搜索和分页。 通过可选查询参数 keyword、page、page_size,客户端可以 GET /api/v1/students?keyword=张&page=1&page_size=10,返回体里除了 items 还带有 total、page、page_size。分页是几乎所有真实接口都会有的能力,别看它简单,它教会了你「查询参数在 URL 中如何声明和约束」(比如 page_size 最大 100,超了自动 422)。
第二,路径参数的顺序。 泛化的 /{student_id} 不能和精确的路由冲突,FastAPI 会按注册顺序匹配,因此我们把列表查询和单个查询天然区分开,不会出现歧义。
第三,状态码的语义约定。 新增返回 201 Created,删除返回 204 No Content(无响应体),查询不到返回 404 Not Found,请求体为空返回 400 Bad Request。规范的状态码让调用方不用解析响应体就知道结果,这也是 API 设计成熟度的体现。
第四,更新接口采用「部分更新」语义。 客户端可以只传 {"age": 17},其余字段保持原值。实现方式是用 exclude_unset=True 只取请求中显式出现的字段,配合存储层的字段合并逻辑,用很小的代码量完成了「局部更新」这个真实业务高频需求。
七、Swagger 文档:零成本交付 API 说明
这是本项目最惊艳的部分。项目代码写完的那一刻,API 文档就已经存在了。访问 http://127.0.0.1:8000/docs,你会得到一个交互式的 Swagger UI:
- 每个接口的方法、路径、标签、描述都自动列出;
- 点击接口可以展开参数说明、请求体示例、响应体结构;
- 直接在页面上点击「Try it out」就能发起真实请求,输入 JSON 点击 Execute 即可看到请求与响应;
- 同时还有 ReDoc 的另一种排版风格,以及
/openapi.json的机器可读原始定义。
这套文档完全没有手写一行业务注记之外的内容,全部由 FastAPI 根据函数签名、类型注解、summary、description 自动生成。这意味着:文档永远和代码同步,永远不会过期。 想想手动维护接口文档的团队有多痛苦——写完接口忘了更新文档,前端照着旧文档联调,凌晨两点对齐字段……而基于 OpenAPI 的自动化文档把这整类问题消灭在了摇篮里。
更妙的是,OpenAPI 规范是行业标准,GitLab、SwaggerHub、Postman 都能直接导入。本文的 Swagger 定义甚至可以一键导出,团队协作、前后端对接效率直接上一个台阶。
八、测试:让代码有底气
光能跑还不够,我为本项目编写了 14 个 pytest 测试用例,覆盖了主要的分支场景:
- 健康检查、新增成功、ID 自增正确性;
- 非法性别、年龄超界返回 422;
- 分页正确性、关键字模糊搜索;
- 查询不存在返回 404;
- 更新成功、空请求体返回 400、更新不存在返回 404;
- 删除成功返回 204、删除不存在返回 404;
- 最后验证 OpenAPI 文档中确实包含全部路由。
测试代码用的是 fastapi.testclient,它在内存中模拟 HTTP 请求,不需要真的启动端口,速度快、无副作用。每个用例前后会重置存储层,保证用例之间互不干扰:
@pytest.fixture(autouse=True)
def clear_store():
student_store._students.clear()
student_store._next_id = 1
yield
...
这 14 个测试不是摆设。它们验证了「接口说的就是代码做的」——特别是在之后把内存存储换成数据库时,只要测试全绿,就说明重构没有破坏任何行为。自动化测试是重构的底气,这在大厂里是基本要求。
九、完整运行体验
我们把整个流程串起来,你可以在自己的电脑上亲手跑一遍:
pip install -r requirements.txt
uvicorn app.main:app --reload --port 8000
然后打开浏览器访问 http://127.0.0.1:8000/docs,或者在终端用 curl:
curl -X POST http://127.0.0.1:8000/api/v1/students \
-H "Content-Type: application/json" \
-d '{"name":"小明","age":16,"gender":"男","grade":"高一(1)班"}'
curl http://127.0.0.1:8000/api/v1/students
curl -X PUT http://127.0.0.1:8000/api/v1/students/1 -H "Content-Type: application/json" -d '{"age":17}'
curl -X DELETE http://127.0.0.1:8000/api/v1/students/1
不需要 MySQL、不需要 Redis,纯 Python 就能体验到「后端接口被真实调用、参数被真实校验、文档被真实展示」的完整闭环。
十、从演示到生产:下一步怎么走
内存存储虽然爽快,但重启即丢失的特性决定了它只适合学习演示。从这一小步出发,未来的路径其实非常清晰:
- 引入数据库:用 SQLModel / SQLAlchemy 把
store.py的内部实现替换为真实数据库,接口层一行不改; - 增加鉴权:用 JWT 实现登录态,为接口加上权限控制;
- 接口安全:字段校验、SQL 注入防护、限流策略;
- 自动化部署:写一个 Dockerfile,
docker build && docker run -p 8000:8000,再上 CI/CD 流水线; - 补充更多业务:学生选课、成绩管理、考勤……一个完整的学生管理系统就在这些细节里慢慢长出来了。
每一次演进,都建立在今天这条「路由—模型—存储—文档」的主干上。学后端,不是从第一天就背会所有框架,而是先让一条最简链路跑通,再一个环节一个环节地把它变厚。
十一、写在最后
回顾这个项目,我们其实只做了五件事:定义模型、实现存储、写路由、自动文档、写测试——然后就得到了一个结构完整、可运行、可演示、可扩展的后端服务。它不复杂,但真实;它很迷你,但链条完整。
如果你正在学习 Web 后端,我强烈建议你也亲手复刻一遍这个小项目,然后尝试回答这三个问题:
- 如果把内存列表换成 SQLite,
store.py里哪些方法会变?哪些不会变? - 如果新增一个「按班级查询」的接口,需要改哪几个文件?
- 如果前端要调用这些接口,
/openapi.json里能提供多少信息?
把这些想明白,你的后端入门就算真正走通了。剩下的事情,交给时间、交给项目、交给不断练习就好。
完整代码已同步发布在代码仓库,欢迎 Star、Fork,也欢迎在评论区交流你的第一个 FastAPI 项目。
本文为技术学习与分享用途,示例代码可直接在本地运行体验。
更多推荐


所有评论(0)