当前位置: 首页 > news >正文

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

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

上一篇里,我把FastAPIFlaskStreamlit的定位做了一次整体梳理。

如果说第 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/docs
  • http://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 的差异到底应该怎么理解。

http://www.jsqmd.com/news/657712/

相关文章:

  • 3D 地球卫星轨道可视化平台开发 Day1(3D 场景、卫星渲染与筛选交互实现)
  • 从LLM幻觉到生产级健壮性,智能代码生成错误检测与修复全链路落地手册,覆盖GitHub Copilot/CodeWhisperer/Tabnine三大引擎
  • c++任意精度定点类型说明
  • 10. 如何批量处理圆角和倒角? I ANSA 设计小诀窍系列
  • 2026年4月怎么集成OpenClaw?华为云7分钟小白流程+大模型APIKey、Skill整合
  • 手搓STM32H743开源飞控系列教程---(七) 从零到一:三种固件烧录方式全场景实战解析
  • 3D地球卫星轨道可视化平台开发Day2(轨道错位Bug修复+模块化结构优化)
  • 2026 年优质农家乐推荐榜:杭州临安双福居农家乐领衔,精选品质之选 - 海棠依旧大
  • 【紧急预警】AI代码提交正在污染你的主干分支:3步紧急隔离+4层防御机制已验证
  • 5分钟上手LogcatReader:安卓设备日志查看神器
  • CentOS7.9 LVM生产环境扩容【KVM虚拟化需要】20260415001篇
  • LX Music Desktop:免费开源跨平台音乐播放器的完整解决方案
  • 打破“存储墙”,为AI硬件提供新路径
  • XHS-Downloader深度解析:小红书内容采集的3大核心技术架构与5倍性能优化方案
  • Super Qwen Voice World智能语音助手开发:基于Python的完整项目实战
  • 大厂 Multi-Agent 落地经验:字节跳动智能创作平台的架构拆解
  • MOPSO算法实战:如何用它搞定你的多目标优化项目?(从理论到调参全解析)
  • 从开发包到业务角色,真正把 Business Catalog 做通的一整条链路
  • 深度剖析:LangGraph中的状态管理与循环逻辑
  • Rsync服务架构配置详解【20260416001篇】-Rsync+inotify版本
  • RAG基本流程
  • XPM_MEMORY_SDPRAM:从参数解析到高效配置的实战指南
  • RequestAttributes , ServletRequestAttributes学习
  • Python实现图形化井字棋——人机对战
  • 从JTAG到EJTAG:揭秘龙芯处理器片上调试的硬件基石
  • 大模型RAG (一)
  • 2026 学术降维打击:9 大 AI 查重降重工具,从重复率 99% 到安全过审全攻略
  • 大模型的参数量-为什么 24B 是一个“甜蜜点“?
  • 5分钟快速上手:开源视频智能分析工具的完整指南
  • vivo X300 Ultra长焦套件集市游玩体验佳,小巧轻便成家庭出游必备!