用FastAPI从0到1写一个真正可用的接口服务

张开发
2026/4/19 22:24:11 15 分钟阅读

分享文章

用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 很适合拿来做一件事把“接口应该是什么样”这件事从口头约定变成代码里的明确规则。二、先搭一个最小可运行环境先装依赖。pipinstallfastapiuvicorn[standard]如果你准备新建一个项目目录可以先简单一点book-api/ ├── main.py └── requirements.txt其中main.py是入口文件先写一个最小版本fromfastapiimportFastAPI appFastAPI()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 appFastAPI(titleBook API,version1.0.0,description一个用于演示 FastAPI 核心功能的图书接口服务,)classBookCreate(BaseModel):title:strField(...,min_length1,max_length100,description书名)author:strField(...,min_length1,max_length50,description作者)price:floatField(...,gt0,description价格)category:Literal[Python,Web,AI,Database]classBook(BookCreate):id:intbooks:list[Book][Book(id1,titleFluent Python,authorLuciano Ramalho,price88.0,categoryPython),Book(id2,titleFastAPI 实战,author张三,price59.9,categoryWeb),]这段代码的价值非常大因为它已经把接口规则写出来了title不能为空author不能为空price必须大于 0category只能是几个固定值之一。也就是说数据是否合法不再靠你在函数里写一堆if去手动判断而是直接交给模型声明来处理。这就是 FastAPI 很舒服的一点接口边界写得越清楚后面越省事。五、开始写接口先做查询再做写入1. 查询图书列表app.get(/books,response_modellist[Book],summary获取图书列表)deflist_books(category:str|NoneQuery(defaultNone,description按分类筛选)):ifcategoryisNone:returnbooksreturn[bookforbookinbooksifbook.categorycategory]这里有两个很值得注意的点Query(...)用来声明查询参数response_modellist[Book]用来约束返回结构。response_model很重要。它不仅影响文档展示还能帮你把输出结构控制得更稳定避免接口返回越来越随意。2. 按 ID 查询单本图书app.get(/books/{book_id},response_modelBook,summary根据 ID 获取图书)defget_book(book_id:intPath(...,gt0,description图书 ID)):forbookinbooks:ifbook.idbook_id:returnbookraiseHTTPException(status_code404,detailBook not found)这里的重点是Path(...)和HTTPExceptionPath(...)让路径参数也能直接做校验HTTPException用来返回明确的错误信息和状态码。这样当前端传了非法 ID或者请求了不存在的数据时接口行为会更清晰。3. 新增图书app.post(/books,response_modelBook,status_codestatus.HTTP_201_CREATED,summary新增图书)defcreate_book(payload:BookCreate):new_idmax(book.idforbookinbooks)1ifbookselse1bookBook(idnew_id,**payload.model_dump())books.append(book)returnbook这里的关键点是请求体直接使用BookCreate返回结构使用Book状态码明确设为201 Created。这其实就是一个很典型的“请求模型和响应模型分离”思路创建时不需要前端传id返回时又需要把id带回去。如果你以后接数据库、做审计字段、做用户信息隔离这种分层会非常有用。4. 更新图书app.put(/books/{book_id},response_modelBook,summary更新图书)defupdate_book(book_id:int,payload:BookCreate):forindex,bookinenumerate(books):ifbook.idbook_id:updated_bookBook(idbook_id,**payload.model_dump())books[index]updated_bookreturnupdated_bookraiseHTTPException(status_code404,detailBook not found)5. 删除图书app.delete(/books/{book_id},summary删除图书)defdelete_book(book_id:int):forindex,bookinenumerate(books):ifbook.idbook_id:delbooks[index]return{message:Book deleted successfully}raiseHTTPException(status_code404,detailBook not found)到这里一个最基本但已经可用的 CRUD 接口服务就齐了。六、把完整代码放在一起你就能直接跑为了方便你复制和验证我把完整示例合在一起fromtypingimportLiteralfromfastapiimportFastAPI,HTTPException,Path,Query,statusfrompydanticimportBaseModel,Field appFastAPI(titleBook API,version1.0.0,description一个用于演示 FastAPI 核心功能的图书接口服务,)classBookCreate(BaseModel):title:strField(...,min_length1,max_length100,description书名)author:strField(...,min_length1,max_length50,description作者)price:floatField(...,gt0,description价格)category:Literal[Python,Web,AI,Database]classBook(BookCreate):id:intbooks:list[Book][Book(id1,titleFluent Python,authorLuciano Ramalho,price88.0,categoryPython),Book(id2,titleFastAPI 实战,author张三,price59.9,categoryWeb),]app.get(/)defroot():return{message:Book API is running}app.get(/books,response_modellist[Book],summary获取图书列表)deflist_books(category:str|NoneQuery(defaultNone,description按分类筛选)):ifcategoryisNone:returnbooksreturn[bookforbookinbooksifbook.categorycategory]app.get(/books/{book_id},response_modelBook,summary根据 ID 获取图书)defget_book(book_id:intPath(...,gt0,description图书 ID)):forbookinbooks:ifbook.idbook_id:returnbookraiseHTTPException(status_code404,detailBook not found)app.post(/books,response_modelBook,status_codestatus.HTTP_201_CREATED,summary新增图书)defcreate_book(payload:BookCreate):new_idmax(book.idforbookinbooks)1ifbookselse1bookBook(idnew_id,**payload.model_dump())books.append(book)returnbookapp.put(/books/{book_id},response_modelBook,summary更新图书)defupdate_book(book_id:int,payload:BookCreate):forindex,bookinenumerate(books):ifbook.idbook_id:updated_bookBook(idbook_id,**payload.model_dump())books[index]updated_bookreturnupdated_bookraiseHTTPException(status_code404,detailBook not found)app.delete(/books/{book_id},summary删除图书)defdelete_book(book_id:int):forindex,bookinenumerate(books):ifbook.idbook_id:delbooks[index]return{message:Book deleted successfully}raiseHTTPException(status_code404,detailBook 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 的差异到底应该怎么理解。

更多文章