用FastAPI从0到1写一个真正可用的接口服务
用 FastAPI 从 0 到 1 写一个真正可用的接口服务
上一篇里,我把FastAPI、Flask和Streamlit的定位做了一次整体梳理。
如果说第 1 篇解决的是“到底该怎么选”,那这一篇就开始真正落地:如果你已经确定自己要做接口服务,FastAPI 应该怎么入门,才能尽快写出一个像样的项目?
很多人第一次接触 FastAPI,会觉得它“看起来很简单”。
确实,写一个最基础的接口并不难;但从“能跑”到“可用”,中间其实差着几个关键台阶:
- 请求参数能不能自动校验;
- 返回结构是不是清晰稳定;
- 文档能不能自动生成;
- 别人拿到你的接口后,能不能直接联调;
- 代码后面还能不能继续扩展。
这篇文章我不准备只写一个Hello World,而是带你从 0 到 1 写一个真正可用的图书接口服务,把 FastAPI 最核心的开发体验一次走通。
一、为什么很多人第一次做 API,会喜欢 FastAPI
如果你做的是前后端分离项目、移动端后端、小程序接口,或者想把某段 Python 能力封装成服务,你最常见的需求通常是:
- 接口参数要明确;
- 非法输入要被拦住;
- 返回结构要统一;
- 文档最好自动生成;
- 调试尽量不要太折腾。
FastAPI 在这些点上给出的体验,确实非常顺手。
它的核心优势不只是“性能不错”,而是:
- 用 Python 类型注解描述参数和数据结构;
- 用
Pydantic自动完成校验和序列化; - 自动生成 Swagger 文档和 ReDoc 文档;
- 写法直观,适合把接口边界写清楚。
换句话说,FastAPI 很适合拿来做一件事:
把“接口应该是什么样”这件事,从口头约定,变成代码里的明确规则。
二、先搭一个最小可运行环境
先装依赖。
pipinstallfastapi"uvicorn[standard]"如果你准备新建一个项目,目录可以先简单一点:
book-api/ ├── main.py └── requirements.txt其中main.py是入口文件,先写一个最小版本:
fromfastapiimportFastAPI app=FastAPI()@app.get("/")defroot():return{"message":"Book API is running"}启动命令:
uvicorn main:app--reload启动后访问:
http://127.0.0.1:8000/http://127.0.0.1:8000/docshttp://127.0.0.1:8000/redoc
很多人第一次打开/docs时会眼前一亮,因为这意味着你不需要手写一份接口文档,再让前端照着猜参数。只要接口和数据模型写清楚,文档就能自动跟上。
三、一个真正可用的接口服务,至少要具备什么
如果只是为了演示,写几个路由函数就够了。
但如果想让这个接口服务更像一个“能给别人用”的项目,至少要有下面这些东西:
- 明确的请求模型;
- 明确的响应模型;
- 合理的状态码;
- 常见错误的处理方式;
- 可调试、可阅读的自动文档。
所以接下来,我们用一个很小的图书管理 API 把这些点串起来。
目标很简单:
- 查询图书列表;
- 按 ID 查询图书;
- 新增图书;
- 更新图书;
- 删除图书。
为了让示例容易理解,这里先不用数据库,直接用内存列表模拟数据。这样你可以把注意力集中在 FastAPI 的核心机制上。
四、先定义数据模型:这是 FastAPI 最关键的一步
很多人写接口时,一上来先写路由;但在 FastAPI 里,先把数据模型定义好,通常更顺。
来看一版完整示例:
fromtypingimportLiteralfromfastapiimportFastAPI,HTTPException,Path,Query,statusfrompydanticimportBaseModel,Field app=FastAPI(title="Book API",version="1.0.0",description="一个用于演示 FastAPI 核心功能的图书接口服务",)classBookCreate(BaseModel):title:str=Field(...,min_length=1,max_length=100,description="书名")author:str=Field(...,min_length=1,max_length=50,description="作者")price:float=Field(...,gt=0,description="价格")category:Literal["Python","Web","AI","Database"]classBook(BookCreate):id:intbooks:list[Book]=[Book(id=1,title="Fluent Python",author="Luciano Ramalho",price=88.0,category="Python"),Book(id=2,title="FastAPI 实战",author="张三",price=59.9,category="Web"),]这段代码的价值非常大,因为它已经把接口规则写出来了:
title不能为空;author不能为空;price必须大于 0;category只能是几个固定值之一。
也就是说,数据是否合法,不再靠你在函数里写一堆if去手动判断,而是直接交给模型声明来处理。
这就是 FastAPI 很舒服的一点:接口边界写得越清楚,后面越省事。
五、开始写接口:先做查询,再做写入
1. 查询图书列表
@app.get("/books",response_model=list[Book],summary="获取图书列表")deflist_books(category:str|None=Query(default=None,description="按分类筛选")):ifcategoryisNone:returnbooksreturn[bookforbookinbooksifbook.category==category]这里有两个很值得注意的点:
Query(...)用来声明查询参数;response_model=list[Book]用来约束返回结构。
response_model很重要。它不仅影响文档展示,还能帮你把输出结构控制得更稳定,避免接口返回越来越随意。
2. 按 ID 查询单本图书
@app.get("/books/{book_id}",response_model=Book,summary="根据 ID 获取图书")defget_book(book_id:int=Path(...,gt=0,description="图书 ID")):forbookinbooks:ifbook.id==book_id:returnbookraiseHTTPException(status_code=404,detail="Book not found")这里的重点是Path(...)和HTTPException:
Path(...)让路径参数也能直接做校验;HTTPException用来返回明确的错误信息和状态码。
这样当前端传了非法 ID,或者请求了不存在的数据时,接口行为会更清晰。
3. 新增图书
@app.post("/books",response_model=Book,status_code=status.HTTP_201_CREATED,summary="新增图书")defcreate_book(payload:BookCreate):new_id=max(book.idforbookinbooks)+1ifbookselse1book=Book(id=new_id,**payload.model_dump())books.append(book)returnbook这里的关键点是:
- 请求体直接使用
BookCreate; - 返回结构使用
Book; - 状态码明确设为
201 Created。
这其实就是一个很典型的“请求模型和响应模型分离”思路:
- 创建时不需要前端传
id; - 返回时又需要把
id带回去。
如果你以后接数据库、做审计字段、做用户信息隔离,这种分层会非常有用。
4. 更新图书
@app.put("/books/{book_id}",response_model=Book,summary="更新图书")defupdate_book(book_id:int,payload:BookCreate):forindex,bookinenumerate(books):ifbook.id==book_id:updated_book=Book(id=book_id,**payload.model_dump())books[index]=updated_bookreturnupdated_bookraiseHTTPException(status_code=404,detail="Book not found")5. 删除图书
@app.delete("/books/{book_id}",summary="删除图书")defdelete_book(book_id:int):forindex,bookinenumerate(books):ifbook.id==book_id:delbooks[index]return{"message":"Book deleted successfully"}raiseHTTPException(status_code=404,detail="Book not found")到这里,一个最基本但已经可用的 CRUD 接口服务就齐了。
六、把完整代码放在一起,你就能直接跑
为了方便你复制和验证,我把完整示例合在一起:
fromtypingimportLiteralfromfastapiimportFastAPI,HTTPException,Path,Query,statusfrompydanticimportBaseModel,Field app=FastAPI(title="Book API",version="1.0.0",description="一个用于演示 FastAPI 核心功能的图书接口服务",)classBookCreate(BaseModel):title:str=Field(...,min_length=1,max_length=100,description="书名")author:str=Field(...,min_length=1,max_length=50,description="作者")price:float=Field(...,gt=0,description="价格")category:Literal["Python","Web","AI","Database"]classBook(BookCreate):id:intbooks:list[Book]=[Book(id=1,title="Fluent Python",author="Luciano Ramalho",price=88.0,category="Python"),Book(id=2,title="FastAPI 实战",author="张三",price=59.9,category="Web"),]@app.get("/")defroot():return{"message":"Book API is running"}@app.get("/books",response_model=list[Book],summary="获取图书列表")deflist_books(category:str|None=Query(default=None,description="按分类筛选")):ifcategoryisNone:returnbooksreturn[bookforbookinbooksifbook.category==category]@app.get("/books/{book_id}",response_model=Book,summary="根据 ID 获取图书")defget_book(book_id:int=Path(...,gt=0,description="图书 ID")):forbookinbooks:ifbook.id==book_id:returnbookraiseHTTPException(status_code=404,detail="Book not found")@app.post("/books",response_model=Book,status_code=status.HTTP_201_CREATED,summary="新增图书")defcreate_book(payload:BookCreate):new_id=max(book.idforbookinbooks)+1ifbookselse1book=Book(id=new_id,**payload.model_dump())books.append(book)returnbook@app.put("/books/{book_id}",response_model=Book,summary="更新图书")defupdate_book(book_id:int,payload:BookCreate):forindex,bookinenumerate(books):ifbook.id==book_id:updated_book=Book(id=book_id,**payload.model_dump())books[index]=updated_bookreturnupdated_bookraiseHTTPException(status_code=404,detail="Book not found")@app.delete("/books/{book_id}",summary="删除图书")defdelete_book(book_id:int):forindex,bookinenumerate(books):ifbook.id==book_id:delbooks[index]return{"message":"Book deleted successfully"}raiseHTTPException(status_code=404,detail="Book not found")保存成main.py之后,执行:
uvicorn main:app--reload然后你就可以直接打开/docs来测试所有接口。
七、为什么说自动文档是 FastAPI 的一个大优势
很多教程提到 FastAPI 的自动文档时,往往一笔带过。但在真实开发里,这个能力其实非常实用。
因为它解决的不是“看起来高级”,而是几个特别现实的问题:
- 你不用再手动维护一份容易过期的接口说明;
- 前端可以直接看参数、类型、示例和返回结构;
- 测试时可以直接在页面里调接口;
- 团队协作时,沟通成本会低很多。
对于个人项目来说,自动文档的价值是让你省时间;
对于团队项目来说,它的价值是减少误解。
很多人第一次用 FastAPI,真正留下好印象的点,不是语法本身,而是这种“接口和文档自然同步”的开发体验。
八、初学者最容易忽略的几个点
1. 只写路由,不写数据模型
这是最常见的问题。
如果你只是把请求体当成一个普通字典去收,短期看起来更快,但很快你就会发现:
- 参数是否合法没人兜底;
- 文档信息不完整;
- 代码越写越散;
- 联调时容易出现边界不一致。
所以在 FastAPI 里,模型不是附属品,而是接口设计本身的一部分。
2. 不区分请求模型和响应模型
很多初学者会让前端传什么,就原样返回什么。
这在简单演示里没问题,但到了真实项目里,通常会有这些需求:
- 创建时不传
id; - 返回时要补
id; - 某些内部字段不能直接暴露;
- 不同接口的返回粒度不一样。
所以请求和响应分开定义,是一个非常值得尽早养成的习惯。
3. 以为 FastAPI 的价值只是“异步”和“性能”
FastAPI 当然支持异步,也有不错的性能表现。
但对大多数业务项目来说,它更重要的价值其实是:
- 接口定义清晰;
- 参数校验自然;
- 文档自动生成;
- 开发协作成本低。
如果把注意力只放在“快不快”,反而会错过它最有实际意义的部分。
九、什么时候适合用 FastAPI,什么时候不要硬上
到这里你会发现,FastAPI 特别适合下面这些场景:
- 做 REST API;
- 做前后端分离后端;
- 做微服务;
- 把数据处理、模型推理、业务逻辑封装成 HTTP 服务;
- 需要比较清晰的接口边界和文档能力。
但它也不是所有项目的最优解。
比如:
- 如果你只是想快速做一个带表单和结果页的小工具,
Streamlit可能更快; - 如果你想先用非常轻的方式理解 Web 服务结构,
Flask依然很好; - 如果你的重点根本不在“接口治理”,那就没必要为了追新而硬套 FastAPI。
框架选型这件事,最终还是回到那个问题:
你现在最想解决的,究竟是 API 规范问题,还是页面交互问题,还是快速验证问题。
十、写在最后
如果你以前对后端接口开发的印象还是“写几个路由、返回几个 JSON”,那 FastAPI 很适合帮你建立一个更现代的 API 开发认知:
- 参数有定义;
- 输入有校验;
- 输出有约束;
- 文档能自动同步;
- 接口能直接调试。
它并没有把后端开发变简单到“没有门槛”,但它确实把很多原本零散、容易出错的工作,整合成了一套更顺手的开发流程。
对于刚入门 Python Web 的人来说,我很建议你至少亲手把这样一个小项目跑一遍。等你真正写过、调过、踩过参数校验和文档联调的坑之后,你对 FastAPI 的理解会比看十篇概念文章都扎实。
下一篇,我会继续写Flask:为什么它到今天依然值得学,以及它和 FastAPI 的差异到底应该怎么理解。
