FastAPI 实战指南:手把手带你从零搭建优雅的 RESTful 服务
来源:CSDN
作者:燐妤
原文链接:点击此处
目录
[一、前言:为什么选择 FastAPI](#一、前言:为什么选择 FastAPI)
[三、路径参数:让 URL 动起来](#三、路径参数:让 URL 动起来)
[五、请求体:用 Pydantic 模型拥抱 JSON](#五、请求体:用 Pydantic 模型拥抱 JSON)
[六、自动生成的 API 文档:Swagger & ReDoc](#六、自动生成的 API 文档:Swagger & ReDoc)
[八、直接拿 Request 对象:更自由的数据获取](#八、直接拿 Request 对象:更自由的数据获取)
[九、回归本质:RESTful 设计的最佳实践](#九、回归本质:RESTful 设计的最佳实践)
一、前言:为什么选择 FastAPI
去年团队决定把一部分旧 Flask 服务重写,技术选型时组长把 FastAPI 推到了桌面上。我当时心想:"又是个网红框架,能比 Flask 好到哪去?" 直到我写完第一个接口,看到 Swagger UI 自动蹦出来,所有请求参数和返回格式一目了然,不用再手动写接口文档,也不用担心前端哥传错参数类型,我才真香了。
FastAPI 是 2018 年出生的现代 Python Web 框架,它专门为 API 而生,底层依赖两个明星库:Starlette(负责网络层,异步高性能)和 Pydantic(数据验证,利用类型注解)。只要你的 Python 版本 ≥ 3.8,一条命令就能开跑。它最打动开发者的四个点:
- 快:异步处理,性能比肩 Node.js 和 Go,压测数据很能打。
- 自动文档:代码写完,
/docs和/redoc两个界面直接生成,支持在线测试。 - 类型提示驱动:写 Python 类型注解就能实现参数校验,IDE 智能提示也很舒服。
- 原生异步:
async/await写起来就像同步代码,特别适合 IO 密集场景(调数据库、请求微服务等)。
如果你是做前后端分离、微服务、或者需要处理大量 JSON 的接口,FastAPI 会让你的开发体验上一个台阶。
二、环境准备与第一个接口
先装上两个库,一台机器上搞定开发环境:
Bash
python
pip install fastapi
pip install "uvicorn[standard]"
uvicorn 是 ASGI 服务器,注意是 ASGI 不是 Flask 用的 WSGI。ASGI 天生支持异步并发,WSGI 则是一把梭的同步模型。FastAPI 应用就用 uvicorn 跑,生产环境还可以用 hypercorn。
接下来写一个 main.py:
Python
python
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/hello/{name}")
async def say_hello(name: str):
# 路径参数自动传给 name
return {"message": f"Hello {name}"}
案例演示:

启动方式特别简单,在终端里敲:
Bash
python
uvicorn main:app --reload
main是文件名(main.py)app是 FastAPI 实例的变量名--reload代表代码有变动自动重启,只建议开发用
或者你可以在代码里直接写启动语句,这样右键运行也更方便:
Python
python
import uvicorn
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
效果演示:


打开浏览器访问 http://127.0.0.1:8000,你就能看到 {"message":"Hello World"}。再试试 /hello/CSDN,输出 {"message":"Hello CSDN"}。
常用 HTTP 方法装饰器一览,都是 RESTful 里的老面孔:
| 装饰器 | 用途 |
|---|---|
@app.get() |
获取资源 |
@app.post() |
创建资源 |
@app.put() |
全量更新资源 |
@app.patch() |
部分更新资源 |
@app.delete() |
删除资源 |
三、路径参数:让 URL 动起来
路径参数就是把值嵌在 URL 路径里,比如 /items/100 里的 100 就是动态的商品 ID。
Python
python
@app.get("/items/{item_id}")
async def read_item(item_id: int):
# 注意类型声明为 int,FastAPI 会自动把字符串"100"转成整数
return {"item_id": item_id}
如果传的不是数字,比如 /items/abc,FastAPI 直接返回 422 校验错误,不用你写任何判断。这就是类型提示的威力。
常见误区:路径参数如果类型写错,比如后台期望 int 却传了 str,开发时直接报错,线上会返回 422。所以养成声明类型的习惯,否则接口容错性会很差。
四、查询参数:灵活的可选输入
查询参数就是 URL 里 ? 后面的部分,比如 /items/100?q=python&page=2。在 FastAPI 里,只要函数参数不是路径参数,它就会自动解释成查询参数。
Python
python
from typing import Union
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Union[str, None] = None, page: int = 1):
# q 是可选的查询参数,默认 None
# page 默认1
return {"item_id": item_id, "q": q, "page": page}
这里的 Union[str, None] 表示 q 要么是字符串,要么是 None。Python 3.10+ 可以直接写 str | None。如果不给 q 传值,它就用默认值 None,接口照样正常工作。
为什么这么设计? 让调用方可以按需筛选、分页,服务端不用写一堆 if 判断参数是否存在。当你要做搜索、过滤功能时,查询参数是最自然的选择。
五、请求体:用 Pydantic 模型拥抱 JSON
光有 URL 参数还不够,创建用户、提交订单这些操作需要传一个 JSON 对象。FastAPI 结合 Pydantic 的 BaseModel,把请求体定义得明明白白。
Python
python
from pydantic import BaseModel
from typing import Union
class Item(BaseModel):
name: str
price: float
is_offer: Union[bool, None] = None # 可选字段
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
# item 会自动从请求体 JSON 解析成 Item 实例
# 如果传过来的 JSON 缺少 name 或 price,会直接 422 报错
return {"item_name": item.name, "item_id": item_id, "offer": item.is_offer}
用 Postman 或 Swagger 发一个 PUT 请求到 /items/123,请求体:
JSON
python
{
"name": "《FastAPI实战》",
"price": 59.9,
"is_offer": true
}
FastAPI 在背后做的事:
- JSON 字符串 → Python 字典 →
Item对象 - 检查
name必须是字符串,price必须是浮点数 is_offer缺了也没事,默认None- 如果
price传了"abc",直接返回 422 并告诉你错在哪
嵌套模型也非常简单,比如用户有收货地址:
Python
python
class Address(BaseModel):
city: str
street: str
class User(BaseModel):
name: str
age: int
address: Address
这样前端传来的 JSON 再复杂也能被结构化校验,配合自动生成的文档,前后端沟通成本直线下降。
六、自动生成的 API 文档:Swagger & ReDoc
启动服务后,直接访问 http://127.0.0.1:8000/docs,你会看到一个 Swagger UI 界面,上面列出了所有接口,点开就能输入参数并发送请求。另一个 http://127.0.0.1:8000/redoc 则是更偏向阅读的文档风格。
这套文档是基于 OpenAPI 规范自动生成的,你代码里写的参数类型、Pydantic 模型、默认值全部变成了 JSON Schema。你甚至不用写一行文档注释,对于接口数量多、频繁变更的项目简直是救命稻草。
实际开发经验:我习惯在调试阶段一直开着 /docs,参数校验失败时直接看错误提示,比看终端日志直观。
七、模块化路由:告别单文件噩梦
当一个系统有十几个接口时,全塞在 main.py 里会非常难维护。FastAPI 提供了 APIRouter 来实现路由拆分,按业务模块组织代码。
假设项目结构:
project/ main.py api/ __init__.py books.py users.py
books.py 示例:
Python
python
from fastapi import APIRouter
router = APIRouter()
@router.get("/books")
async def get_books():
return {"books": ["Python入门", "FastAPI深入"]}
@router.post("/books")
async def create_book(name: str):
# 实际开发会存数据库,这里只演示
return {"msg": f"创建书籍:{name}"}
users.py 类似,用不同的路由前缀。
然后在 main.py 里统一注册:
Python
python
from fastapi import FastAPI
from api.books import router as book_router
from api.users import router as user_router
app = FastAPI()
app.include_router(book_router, prefix="/book", tags=["书籍管理"])
app.include_router(user_router, prefix="/user", tags=["用户管理"])
现在访问 /book/books 就是获取书籍列表,/user/... 则是用户相关。tags 参数会让它们在 Swagger 里分组显示,干净利落。
为什么这么设计? 随着团队人数变多,每个人负责不同模块,拆分路由可以避免 Git 冲突,也让单元测试更容易编写。大型项目还可以进一步按版本分组,比如 prefix="/v1"。
八、直接拿 Request 对象:更自由的数据获取
大部分时候我们使用参数声明就够,但有些场景需要读取请求头、客户端 IP、原始请求体,这时可以直接注入 Request 对象。
Python
python
from fastapi import FastAPI, Request
@app.get("/ip")
async def get_ip(request: Request):
client_host = request.client.host
user_agent = request.headers.get("user-agent")
return {"ip": client_host, "ua": user_agent}
@app.post("/raw")
async def raw_body(request: Request):
body = await request.body() # 原始字节
json_data = await request.json() # 解析成 dict
return {"raw_length": len(body), "parsed": json_data}
常用操作速查:
request.query_params------ 拿到QueryParams对象,可转字典await request.json()------ 异步取值,用于 POST/PUT 这类request.headers------ 请求头字典request.client.host------ 客户端 IP,做频率限制时会用到await request.form()------ 处理表单上传
注意事项:
request.json()只能调用一次,因为流读完后指针就到底了。如果想同时记录日志和交给 Pydantic 模型,要么用依赖注入,要么把请求体先读出来再传递。- 如果你已经用 Pydantic 模型声明了参数,FastAPI 内部已经读了请求体,你就不能再次调用
request.json(),会报错。此时可以用Body或依赖获取原始数据。
九、回归本质:RESTful 设计的最佳实践
虽然 FastAPI 功能强大,但接口设计依旧要遵循 RESTful 规范,否则容易写出"伪 REST"。
| 方法 | 含义 | 幂等性 | 示例 |
|---|---|---|---|
| GET | 获取资源 | 是 | GET /users/1 |
| POST | 创建资源 | 否 | POST /users |
| PUT | 全量更新 | 是 | PUT /users/1 |
| PATCH | 部分更新 | 否 | PATCH /users/1 |
| DELETE | 删除资源 | 是 | DELETE /users/1 |
强调两点:
- 幂等性意味着多次相同请求的结果与一次相同,GET/PUT/DELETE 都应保证。
- 前后端分离模式下,后端只返回 JSON 数据,前端(Vue、React 等)负责渲染,绝不返回 HTML 模板。
FastAPI 天然适合这种模式,你只管把 JSON 返回写清楚,让前端同事拿着 /docs 去联调,再也不需要吼:"接口文档更新了吗!"
十、总结与踩坑指南
一路下来,你会发现 FastAPI 几乎把开发 API 的琐碎事全包了:类型校验、文档生成、异步处理、模块化路由。记忆时可以用一句口诀:Starlette 底座 + Pydantic 校验 + 类型提示 = 自动文档。
动手实践时注意这些容易翻车的点:
- 路径参数类型:一定要明确
int、str,否则可能匹配到错误的路由。 - 请求体复用:同一个 Pydantic 模型如果用于多个接口,后期改字段要小心影响面,推荐用继承或独立模型。
- 依赖注入:
Depends是 FastAPI 的精髓,复用权限校验、数据库连接时能省很多代码,可以慢慢深入。 - 异常处理:用
HTTPException返回明确的状态码和错误信息,而不是直接抛 500。 - CORS 跨域:前后端分离时,务必在
main.py里添加CORSMiddleware,否则前端请求会报跨域错误。
最后建议大家新建一个虚拟环境,把今天所有代码跑一遍,再用 /docs 把每个接口测一测。有任何问题,欢迎在评论区讨论,我会逐一回复。原创不易,如果觉得有帮助,记得点赞收藏支持一下~
每日励志文案:
我爱我缓慢向上的勇气
------《人名日报》