这篇只讲 FastAPI 写接口时最常用的语法。
先把主线记住:
创建 app
-> 定义路由
-> 接收参数
-> 用 Pydantic 校验请求体
-> 返回响应
-> 抛出异常
-> 复用依赖
-> 拆分 Router
FastAPI 的核心不是“装饰器很多”,而是:
Python 类型标注
-> FastAPI 读取类型
-> Pydantic 做数据校验
-> 自动生成 OpenAPI 文档
-> 自动返回 JSON 响应
也就是说,接口规则很多时候不是靠你手写 if 判断,而是写在函数参数、类型注解和 Pydantic 模型里。
一、安装和启动
新项目如果使用 uv,可以这样安装:
uv add "fastapi[standard]"
fastapi[standard] 会安装 FastAPI 以及官方推荐的标准依赖,比如开发服务、表单、文件上传等常见能力需要的包。
如果只是临时学习,也可以用 pip:
pip install "fastapi[standard]"
创建 main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root() -> dict[str, str]:
return {
"message": "Hello FastAPI",
}
启动开发服务:
uv run fastapi dev main.py
如果已经激活虚拟环境,也可以直接:
fastapi dev main.py
访问:
http://127.0.0.1:8000
http://127.0.0.1:8000/docs
/docs 是 FastAPI 根据代码自动生成的交互式接口文档。
也可以用更底层的 Uvicorn 启动:
uv run uvicorn main:app --reload
这里的 main:app 可以这样理解:
main
-> main.py 文件
app
-> main.py 里的 FastAPI 实例
二、创建应用
FastAPI 项目通常从创建应用实例开始:
from fastapi import FastAPI
app = FastAPI()
可以给应用补充一些文档信息:
app = FastAPI(
title="Todo API",
description="一个用于学习 FastAPI 的接口服务",
version="1.0.0",
)
这些信息会展示在 /docs 和 OpenAPI Schema 里。
三、定义路由
FastAPI 使用装饰器定义路由。
@app.get("/users")
def list_users() -> list[dict[str, object]]:
return [
{
"id": 1,
"name": "Alice",
}
]
这段代码表示:
当请求 GET /users 时
-> 执行 list_users 函数
-> 把返回值转成 JSON 响应
常见 HTTP 方法:
@app.get("/users")
def list_users():
return []
@app.post("/users")
def create_user():
return {"id": 1}
@app.put("/users/{user_id}")
def update_user(user_id: int):
return {"id": user_id}
@app.patch("/users/{user_id}")
def patch_user(user_id: int):
return {"id": user_id}
@app.delete("/users/{user_id}")
def delete_user(user_id: int):
return {"deleted": True}
先按 REST 习惯记:
GET
-> 查询
POST
-> 创建
PUT
-> 全量更新
PATCH
-> 局部更新
DELETE
-> 删除
四、路径参数
路径参数写在 URL 路径里,用 {} 表示。
@app.get("/users/{user_id}")
def get_user(user_id: int) -> dict[str, int]:
return {
"id": user_id,
}
请求:
GET /users/100
FastAPI 会把路径里的 100 传给函数参数 user_id。
因为你写了:
user_id: int
所以 FastAPI 会尝试把字符串 "100" 转成整数 100。
如果访问:
GET /users/abc
FastAPI 会自动返回参数校验错误。
多个路径参数:
@app.get("/users/{user_id}/orders/{order_id}")
def get_user_order(user_id: int, order_id: int) -> dict[str, int]:
return {
"user_id": user_id,
"order_id": order_id,
}
路径里的参数名必须和函数参数名对应。
如果要给路径参数加规则,用 Path:
from typing import Annotated
from fastapi import Path
@app.get("/users/{user_id}")
def get_user(
user_id: Annotated[int, Path(ge=1)],
) -> dict[str, int]:
return {
"id": user_id,
}
含义:
user_id 来自路径
类型是 int
值必须大于等于 1
五、查询参数
查询参数就是 URL ? 后面的参数。
@app.get("/users")
def list_users(page: int = 1, size: int = 20) -> dict[str, int]:
return {
"page": page,
"size": size,
}
请求:
GET /users?page=2&size=10
对应:
page = 2
size = 10
FastAPI 的判断规则可以先这样记:
参数出现在路径里
-> 路径参数
参数没有出现在路径里,并且是简单类型(str, int, float, bool)
-> 查询参数
如果参数可以不传,常用 None:
@app.get("/users")
def list_users(keyword: str | None = None) -> dict[str, object]:
return {
"keyword": keyword,
}
str | None = None 表示:
keyword 可以是字符串
keyword 也可以是 None
请求里不传时默认是 None
Python 没有 JavaScript 里的 undefined。接口里表达“没有值”,通常就是 None。
六、查询参数校验
如果只是写:
page: int = 1
只能表达类型和默认值。
如果还想表达最小值、最大值、长度限制、文档描述,可以用 Query。
from typing import Annotated
from fastapi import Query
@app.get("/users")
def list_users(
page: Annotated[int, Query(ge=1)] = 1,
size: Annotated[int, Query(ge=1, le=100)] = 20,
keyword: Annotated[str | None, Query(max_length=20)] = None,
) -> dict[str, object]:
return {
"page": page,
"size": size,
"keyword": keyword,
}
先把 Annotated 理解成“给类型补充额外规则”。
Annotated[int, Query(ge=1)]
-> 参数类型是 int
-> FastAPI 额外要求这个查询参数 >= 1
常见校验规则:
| 参数 | 含义 |
|---|---|
ge | 大于等于 |
gt | 大于 |
le | 小于等于 |
lt | 小于 |
min_length | 最小长度 |
max_length | 最大长度 |
pattern | 正则匹配 |
例如:
keyword: Annotated[str | None, Query(min_length=2, max_length=20)] = None
表示:
keyword 可以不传
如果传了,长度必须在 2 到 20 之间
七、请求体
请求体通常用 Pydantic 模型描述。
from pydantic import BaseModel
class CreateUserRequest(BaseModel):
name: str
age: int
email: str | None = None
@app.post("/users")
def create_user(data: CreateUserRequest) -> dict[str, object]:
return {
"id": 1,
"name": data.name,
"age": data.age,
"email": data.email,
}
请求 JSON:
{
"name": "Alice",
"age": 18
}
FastAPI 会自动做这些事:
读取请求体 JSON
-> 用 CreateUserRequest 校验字段
-> 校验通过后创建 data 对象
-> 把 data 传给 create_user 函数
Pydantic 模型里,没有默认值就是必填字段:
class CreateUserRequest(BaseModel):
name: str
age: int
email: str | None = None
is_active: bool = True
含义:
name 必传
age 必传
email 可不传,默认 None
is_active 可不传,默认 True
字段校验用 Field:
from pydantic import BaseModel, Field
class CreateUserRequest(BaseModel):
name: str = Field(min_length=2, max_length=20)
age: int = Field(ge=0, le=150)
email: str | None = None
可以这样理解:
Query
-> 校验查询参数
Path
-> 校验路径参数
Field
-> 校验 Pydantic 模型字段
Pydantic v2 常用 model_dump() 转成普通字典:
@app.post("/users")
def create_user(data: CreateUserRequest) -> dict[str, object]:
payload = data.model_dump()
return {
"id": 1,
**payload,
}
八、响应数据
FastAPI 可以直接返回 Python 数据结构。
@app.get("/users/{user_id}")
def get_user(user_id: int) -> dict[str, object]:
return {
"id": user_id,
"name": "Alice",
}
常见返回值:
return {"id": 1, "name": "Alice"}
return [{"id": 1}, {"id": 2}]
return {"success": True}
FastAPI 会把这些数据序列化成 JSON 响应。
响应结构建议用 Pydantic 模型表达:
from pydantic import BaseModel
class UserResponse(BaseModel):
id: int
name: str
email: str | None = None
@app.get("/users/{user_id}", response_model=UserResponse)
def get_user(user_id: int) -> dict[str, object]:
return {
"id": user_id,
"name": "Alice",
"password": "should_not_return",
}
response_model 有几个作用:
- 生成接口文档
- 校验响应数据
- 过滤未声明字段
- 让接口返回结构更清楚
上面例子里,password 不在 UserResponse 中,所以最终不会返回给前端。
如果函数返回值类型就是 Pydantic 模型,也可以直接写返回类型:
@app.get("/users/{user_id}")
def get_user(user_id: int) -> UserResponse:
return UserResponse(
id=user_id,
name="Alice",
)
状态码可以用 status 常量:
from fastapi import status
@app.post("/users", status_code=status.HTTP_201_CREATED)
def create_user(data: CreateUserRequest) -> dict[str, object]:
return {
"id": 1,
**data.model_dump(),
}
九、异常处理
接口里如果要主动返回错误状态,可以抛出 HTTPException。
from fastapi import HTTPException
@app.get("/users/{user_id}")
def get_user(user_id: int) -> dict[str, object]:
user = None
if user is None:
raise HTTPException(
status_code=404,
detail="用户不存在",
)
return user
常见状态:
raise HTTPException(status_code=400, detail="参数错误")
raise HTTPException(status_code=401, detail="未登录")
raise HTTPException(status_code=403, detail="无权限")
raise HTTPException(status_code=404, detail="资源不存在")
如果要统一业务异常格式,可以定义自定义异常处理器:
from fastapi import Request
from fastapi.responses import JSONResponse
class BusinessError(Exception):
def __init__(self, message: str) -> None:
self.message = message
@app.exception_handler(BusinessError)
def business_error_handler(
request: Request,
exc: BusinessError,
) -> JSONResponse:
return JSONResponse(
status_code=400,
content={
"code": "BUSINESS_ERROR",
"message": exc.message,
},
)
业务代码里就可以:
raise BusinessError("余额不足")
十、依赖注入 Depends
Depends 是 FastAPI 里非常重要的语法。
它的作用是:把公共逻辑声明成当前接口的依赖。
常见场景:
- 获取当前登录用户
- 校验权限
- 创建数据库会话
- 复用分页参数
- 解析公共请求头
- 读取租户、部门、校区等上下文
示例:
from typing import Annotated
from fastapi import Depends, Header, HTTPException
from pydantic import BaseModel
class CurrentUser(BaseModel):
id: int
name: str
def get_current_user(
authorization: Annotated[str | None, Header()] = None,
) -> CurrentUser:
if authorization is None:
raise HTTPException(status_code=401, detail="未登录")
return CurrentUser(id=1, name="Alice")
@app.get("/profile")
def get_profile(
current_user: Annotated[CurrentUser, Depends(get_current_user)],
) -> CurrentUser:
return current_user
执行过程:
请求 GET /profile
-> FastAPI 发现 current_user 依赖 get_current_user
-> 先执行 get_current_user
-> 从 Header 里读取 authorization
-> 返回 CurrentUser
-> 把 CurrentUser 传给 get_profile
Depends 的重点是声明关系:
这个接口需要 current_user
current_user 由 get_current_user 生成
接口函数不用关心当前用户怎么解析,只要声明自己需要它。
十一、Header、Cookie、Form、File
FastAPI 默认能推断路径参数、查询参数和请求体。
但 Header、Cookie、表单、文件上传这些来源需要明确声明。
Header
from typing import Annotated
from fastapi import Header
@app.get("/headers")
def read_headers(
user_agent: Annotated[str | None, Header()] = None,
) -> dict[str, object]:
return {
"user_agent": user_agent,
}
FastAPI 会把 HTTP 请求头里的 User-Agent 映射到 Python 参数 user_agent。
Cookie
from typing import Annotated
from fastapi import Cookie
@app.get("/cookies")
def read_cookies(
session_id: Annotated[str | None, Cookie()] = None,
) -> dict[str, object]:
return {
"session_id": session_id,
}
Form
from typing import Annotated
from fastapi import Form
@app.post("/login")
def login(
username: Annotated[str, Form()],
password: Annotated[str, Form()],
) -> dict[str, str]:
return {
"username": username,
}
File
from typing import Annotated
from fastapi import File, UploadFile
@app.post("/upload")
async def upload_file(
file: Annotated[UploadFile, File()],
) -> dict[str, object]:
content = await file.read()
return {
"filename": file.filename,
"size": len(content),
}
先记规律:
Header()
-> 从请求头取
Cookie()
-> 从 Cookie 取
Form()
-> 从表单字段取
File()
-> 从上传文件取
十二、同步函数和异步函数
FastAPI 支持普通函数:
@app.get("/sync")
def sync_api() -> dict[str, str]:
return {
"mode": "sync",
}
也支持异步函数:
@app.get("/async")
async def async_api() -> dict[str, str]:
return {
"mode": "async",
}
不要简单理解成“FastAPI 接口必须全部写 async”。
更实际的判断:
调用同步库
-> def
调用异步库,并且需要 await
-> async def
例如:
@app.get("/users")
async def list_users() -> list[dict[str, object]]:
users = await user_repository.find_many()
return users
如果你写了 async def,里面却调用大量阻塞式同步代码,性能不一定更好。
async def 的价值主要在 I/O 等待:
- 异步数据库查询
- 异步 HTTP 请求
- WebSocket
- 流式响应
- 文件或网络 I/O
十三、Router 拆分
项目小的时候,所有代码可以先写在 main.py。
项目变大后,应该拆分 Router。
app/api/v1/routers/users.py:
from fastapi import APIRouter
from pydantic import BaseModel
router = APIRouter(
prefix="/users",
tags=["users"],
)
class CreateUserRequest(BaseModel):
name: str
age: int
@router.get("")
def list_users() -> list[dict[str, object]]:
return []
@router.post("")
def create_user(data: CreateUserRequest) -> dict[str, object]:
return {
"id": 1,
**data.model_dump(),
}
app/main.py:
from fastapi import FastAPI
from app.api.v1.routers import users
app = FastAPI()
app.include_router(users.router)
常见项目结构:
app/
main.py
api/
v1/
routers/
users.py
auth.py
schemas/
user.py
services/
user_service.py
dependencies/
auth.py
简单分工:
main.py
-> 创建 FastAPI 应用,挂载路由、中间件
routers/
-> 定义接口路径、参数、响应状态码
schemas/
-> 定义 Pydantic 请求体、响应体
services/
-> 写业务逻辑
dependencies/
-> 放登录用户、数据库连接、权限等公共依赖
十四、核心语法速记
| 场景 | 写法 |
|---|---|
| 创建应用 | app = FastAPI() |
| 定义 GET 接口 | @app.get("/users") |
| 路径参数 | @app.get("/users/{user_id}") + user_id: int |
| 查询参数 | page: int = 1 |
| 查询参数校验 | Annotated[int, Query(ge=1)] |
| 路径参数校验 | Annotated[int, Path(ge=1)] |
| 请求体 | data: CreateUserRequest |
| 模型字段校验 | Field(min_length=2) |
| 响应模型 | response_model=UserResponse |
| 错误响应 | raise HTTPException(...) |
| 依赖注入 | Annotated[User, Depends(get_user)] |
| 拆分路由 | APIRouter() + app.include_router() |
总结
学习 FastAPI 核心语法时,不要只背装饰器。
真正要掌握的是这条链路:
类型标注
-> 参数解析
-> Pydantic 校验
-> 自动文档
-> JSON 响应
只要这条链路通了,路径参数、查询参数、请求体、响应模型、依赖注入都会变成同一套思路。