第一个FastAPI应用:创建最小API、路由定义、路径参数、查询参数、请求体解析、响应模型基础
FastAPI 之所以能在短时间内成为 Python 后端开发的热门选择,核心在于它同时解决了性能、开发效率与类型安全三大痛点。本章将从一个最小的可运行应用出发,逐步拆解路由、参数解析与响应模型的核心机制。我们不仅要学会“怎么写”,更要理解“为什么这样写”——尤其是 FastAPI 如何借助 Python 类型注解完成请求校验、数据序列化与 API 文档自动生成。这一章是后续所有高级特性(依赖注入、安全认证、数据库集成)的地基,请务必亲手敲一遍代码并观察每一步的交互行为。
上图展示了 FastAPI 的核心数据流:HTTP 请求进入路由层后,由参数解析层根据类型注解完成校验与转换,业务函数处理后,响应模型层负责序列化输出。右侧三个模块(OpenAPI 文档、Pydantic 模型、类型注解)并非独立存在,而是由同一套类型系统驱动——这正是 FastAPI 区别于 Flask、Django 的关键设计哲学。
3.1 最小 API 的创建与运行机制
核心原理:FastAPI 应用本质上是一个 ASGI(Asynchronous Server Gateway Interface)应用实例。通过 FastAPI() 创建应用对象,再使用装饰器注册路由。最小 API 只需要一个路径操作装饰器(如 @app.get("/"))和一个返回字典或 Pydantic 模型的函数。FastAPI 会自动将返回值序列化为 JSON,并设置正确的 Content-Type 头。
# main.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "Hello FastAPI"}
启动命令为 uvicorn main:app --reload。其中 main 是模块文件名,app 是 FastAPI 实例变量名,--reload 开启开发模式热重载。访问 http://127.0.0.1:8000 即可看到 JSON 响应,访问 /docs 可查看自动生成的 Swagger 交互文档。
application/json 响应格式。如果返回 dict,FastAPI 会通过 jsonable_encoder 自动处理 datetime、Decimal 等非原生 JSON 类型。建议从第一个应用开始就使用 Pydantic 模型作为响应类型,而不是裸字典,这样能获得更好的类型提示和文档支持。
3.2 路由定义:从静态到动态
操作流程:路由定义遵循“路径 + HTTP 方法”的映射规则。FastAPI 支持 @app.get()、@app.post()、@app.put()、@app.delete() 等常用装饰器,也支持 @app.api_route() 指定多个方法。路径参数使用花括号 {} 声明,并在函数签名中通过同名参数接收。
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id, "name": f"Item {item_id}"}
这里 item_id: int 的类型注解至关重要——FastAPI 会自动将路径中的字符串转换为 int,如果转换失败(如访问 /items/abc),将返回 422 校验错误,而不是让程序崩溃。路径参数的顺序不影响匹配,但路径中不能有重复的参数名。
| 路由模式 | 示例 | 匹配路径 | 说明 |
|---|---|---|---|
| 静态路径 | /users | /users | 精确匹配 |
| 动态路径 | /users/{user_id} | /users/42 | 路径参数,类型转换 |
| 多段路径 | /files/{file_path:path} | /files/docs/report.pdf | :path 转换器匹配任意多段 |
| 固定前缀 | /api/v1/items | /api/v1/items | 常用于版本控制 |
/users/42),而查询参数是 ? 后的键值对(如 /users?age=30)。路径参数适合标识资源,查询参数适合过滤、排序或分页。如果路径参数声明为 int 但实际传入非数字,FastAPI 会返回 422 错误,这是预期行为,不要试图捕获 ValueError 来“修复”。
3.3 查询参数:可选、默认值与类型校验
核心原理:查询参数是 URL 中 ? 后面的键值对。在 FastAPI 中,只要函数参数没有在路径中声明,就会被自动识别为查询参数。查询参数可以是必填的(无默认值)、可选的(有默认值)或可空的(Optional[type])。FastAPI 会根据类型注解自动解析、转换和校验。
@app.get("/search")
def search_items(q: str | None = None, limit: int = 10, skip: int = 0):
return {"query": q, "limit": limit, "skip": skip}
访问 /search?q=fastapi&limit=5&skip=2 时,FastAPI 会解析出 q="fastapi"、limit=5、skip=2。如果省略 q,则默认为 None;如果 limit 传入非整数(如 limit=abc),返回 422 错误。查询参数还支持 Query 类进行更精细的控制,如最小/最大值、正则校验、别名等。
在真实项目中,查询参数常用于列表接口的分页和过滤。例如一个商品列表接口:
/products?category=electronics&page=2&size=20。建议将 page 和 size 的默认值设为 1 和 20,并使用 Query(ge=1) 限制最小值为 1,避免负数或零导致的异常。对于可选的过滤条件,使用 Optional[str] 类型,这样既能保持 API 的灵活性,又能获得类型安全。
3.4 请求体解析:Pydantic 模型的力量
操作流程:当客户端需要提交结构化数据(如创建用户、更新订单)时,应使用 POST/PUT 方法并携带请求体。FastAPI 要求使用 Pydantic 模型定义请求体结构,然后在函数参数中声明该模型类型。FastAPI 会自动读取请求体 JSON,校验字段类型和约束,并将结果注入到函数参数中。
from pydantic import BaseModel, Field
class UserCreate(BaseModel):
username: str = Field(..., min_length=3, max_length=20)
email: str
age: int = Field(ge=0, le=120)
@app.post("/users/")
def create_user(user: UserCreate):
# 此时 user 已经通过校验,可以直接使用
return {"id": 1, **user.model_dump()}
关键点:Field(..., min_length=3) 中的 ... 表示必填,min_length 和 max_length 是字符串长度约束,ge/le 是数值范围约束。如果请求体缺少必填字段或字段类型错误,FastAPI 返回 422 错误,并附带详细的错误信息(哪个字段、什么原因)。
| 校验场景 | Pydantic 写法 | 错误示例 |
|---|---|---|
| 必填字段 | name: str | 缺少 name 字段 |
| 字符串长度 | Field(min_length=2, max_length=10) | "a" 或 "abcdefghijk" |
| 数值范围 | Field(ge=0, le=100) | -1 或 101 |
| 正则匹配 | Field(pattern=r"^\d{3}-\d{4}$") | "12345" 不匹配 |
| 可选字段 | nickname: str | None = None | 允许缺失或 null |
embed=True 或嵌套模型)。如果同时需要路径参数、查询参数和请求体,路径参数和查询参数应放在请求体参数之前声明。另外,Pydantic v2 中 .dict() 已弃用,应使用 .model_dump()。
3.5 响应模型:控制输出结构与数据安全
核心原理:响应模型(response_model)用于声明 API 返回的数据结构。它有两个核心作用:一是过滤输出字段(防止敏感数据泄露),二是生成准确的 OpenAPI 文档。FastAPI 会使用响应模型对返回值进行序列化,如果返回的字典包含响应模型中未定义的字段,这些字段会被自动过滤掉。
from pydantic import BaseModel
class UserOut(BaseModel):
id: int
username: str
email: str
class UserInDB(UserOut):
hashed_password: str
@app.post("/users/", response_model=UserOut)
def create_user(user: UserCreate):
# 假设内部创建了 UserInDB 对象
user_db = UserInDB(id=1, username=user.username, email=user.email, hashed_password="fakehash")
return user_db # 响应中不会包含 hashed_password
这里 response_model=UserOut 确保即使内部返回了包含 hashed_password 的 UserInDB 对象,最终响应也只包含 id、username、email 三个字段。这是防止密码、密钥等敏感信息泄露的标准做法。
背景与约束:某社交平台需要实现用户注册接口,要求用户名唯一、密码至少 8 位、邮箱格式合法。前端需要清晰的错误提示,后端需要防止密码泄露。
初始目标:设计一个 POST
/users 接口,接收 JSON 请求体,返回创建成功的用户信息(不含密码)。关键输入:请求体
{"username": "alice", "email": "alice@example.com", "password": "secret123"}。分析判断:需要三个模型:
UserCreate(请求体,含密码)、UserInDB(数据库模型,含哈希密码)、UserOut(响应模型,不含密码)。密码字段使用 Field(min_length=8) 校验。实际操作:① 定义三个 Pydantic 模型;② 路由函数接收
UserCreate,内部将密码哈希后构造 UserInDB;③ 返回 UserInDB 对象,但声明 response_model=UserOut。结果对比:响应体为
{"id": 1, "username": "alice", "email": "alice@example.com"},无密码字段。若请求体密码少于 8 位,返回 422 错误,错误信息指明 password 字段不满足最小长度。风险与代价:如果忘记声明
response_model,密码将直接暴露在响应中。建议所有涉及敏感字段的接口强制使用响应模型,并在代码评审中检查。复盘经验:响应模型不仅是文档工具,更是安全边界。将输入模型、数据库模型、输出模型分离,是 FastAPI 项目的标准分层模式。
3.6 常见误区与调试技巧
误区一:混淆路径参数与查询参数。路径参数是 URL 的一部分,查询参数是 ? 后的键值对。如果参数在路径中声明,就不能在函数签名中作为查询参数使用。
误区二:忽略类型注解。FastAPI 完全依赖类型注解进行校验和序列化。如果函数参数不写类型注解,FastAPI 会将其视为查询参数,且不进行任何类型转换。例如 def read_item(item_id): 中的 item_id 会被当作字符串处理。
误区三:请求体与查询参数混用。一个函数中只能有一个 Pydantic 模型作为请求体。如果尝试声明两个模型参数,FastAPI 会报错。解决方案是使用嵌套模型或 embed=True。
调试技巧:当遇到 422 错误时,FastAPI 的响应体包含 detail 数组,其中每个元素包含 loc(错误位置)、msg(错误信息)、type(错误类型)。例如 {"detail":[{"loc":["body","age"],"msg":"Input should be greater than or equal to 0","type":"greater_than_equal"}]}。在开发阶段,建议直接访问 /docs 页面进行交互式调试,FastAPI 会生成可执行的请求示例。
response_model 过滤敏感字段。这三个练习覆盖了本章 80% 的知识点,完成后即可进入下一章学习更高级的依赖注入与数据库集成。