FastAPI 参数接收入门:路径参数、查询参数、请求体参数一次搞懂

1 阅读4分钟

FastAPI 里最常见的三种参数接收方式,其实就是:

  • 路径参数:写在 URL 路径里,例如 /users/123
  • 查询参数:拼在 URL 问号后面,例如 /search?keyword=python&page=1
  • 请求体参数:放在 POST / PUT / PATCH 请求的 JSON body 里

如果你之前写过 Vue Router,会发现 FastAPI 的路径参数和查询参数其实很好理解:路径参数类似 Vue Router 的动态路由,查询参数类似 route.query。而请求体参数,通常配合 Pydantic 模型使用,用来接收前端提交的 JSON 数据。

一、路径参数

路径参数会直接出现在 URL 路径中,FastAPI 使用 {} 来定义。

@app.get("/users/{user_id}")
async def get_user(user_id: int):
    return {"user_id": user_id}

当请求 /users/123 时,user_id 会被解析为 123。
如果加上类型注解,例如 user_id: int,FastAPI 会自动校验类型:

  • /users/123:正常
  • /users/abc:返回 422 错误

多个路径参数可以这样写:

@app.get("/users/{user_id}/posts/{post_id}")
async def get_post(user_id: int, post_id: int):
    return {"user_id": user_id, "post_id": post_id}

如果只想允许固定取值,可以使用 Enum:

from enum import Enum

class ModelName(str, Enum):
    alexnet = "alexnet"
    resnet = "resnet"

@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
    return {"model_name": model_name}

如果需要限制数值范围,可以使用 Path:

from fastapi import Path

@app.get("/items/{item_id}")
async def read_item(
    item_id: int = Path(..., ge=1, le=100, description="商品 ID")
):
    return {"item_id": item_id}

如果路径参数本身包含斜杠,例如 /files/a/b/c.txt,可以使用 path 类型:

@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
    return {"file_path": file_path}

路由顺序也很重要,固定路由要写在动态路由前面:

@app.get("/users/me")
async def read_me():
    return {"user_id": "me"}

@app.get("/users/{user_id}")
async def read_user(user_id: str):
    return {"user_id": user_id}

否则 /users/me 会被 {user_id} 匹配走。

二、查询参数

查询参数不需要写在路由路径里,直接作为函数参数即可。

@app.get("/search")
async def search(keyword: str, page: int = 1):
    return {"keyword": keyword, "page": page}

请求 /search?keyword=python&page=2 时:

  • keyword 为 "python"
  • page 为 2

如果参数有默认值,比如 page: int = 1,前端不传时就会使用默认值。

路径参数和查询参数可以一起使用:

@app.get("/users/{user_id}/posts")
async def get_user_posts(user_id: int, page: int = 1, limit: int = 10):
    return {
        "user_id": user_id,
        "page": page,
        "limit": limit,
    }

对应 URL:

GET /users/123/posts?page=1&limit=10

前端如果使用 axios,可以这样调用:

const userId = 123;

axios.get(`/users/${userId}/posts`, {
  params: { page: 1, limit: 10 },
});

三、请求体参数

请求体参数通常放在 POST / PUT / PATCH 请求的 JSON body 中,FastAPI 一般使用 Pydantic 模型来接收。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    price: float
    description: str | None = None

@app.post("/items/")
async def create_item(item: Item):
    return {"name": item.name, "price": item.price}

前端发送:

{
  "name": "Python 书",
  "price": 59.9
}

FastAPI 会自动解析 JSON、校验类型,并在校验失败时返回 422 错误。

字段校验

可以使用 Field 增加更细的校验规则:

from pydantic import BaseModel, Field

class Item(BaseModel):
    name: str = Field(min_length=1, max_length=50)
    price: float = Field(gt=0)
    description: str | None = None

这里表示:

  • name 不能为空,最多 50 个字符
  • price 必须大于 0

嵌套请求体

如果前端传的是嵌套 JSON,也可以直接建模:

class Address(BaseModel):
    city: str
    street: str

class User(BaseModel):
    name: str
    age: int
    address: Address

前端传:

{
  "name": "小明",
  "age": 20,
  "address": {
    "city": "北京",
    "street": "长安街"
  }
}

后端直接写:

@app.post("/users/")
async def create_user(user: User):
    return user

多个 Pydantic 模型参数

如果函数里写了多个 Pydantic 模型参数,FastAPI 会把它们变成嵌套结构:

class Item(BaseModel):
    name: str
    price: float

class User(BaseModel):
    username: str

@app.post("/orders/")
async def create_order(item: Item, user: User):
    return {"item": item, "user": user}

请求体需要写成:

{
  "item": {
    "name": "书",
    "price": 39
  },
  "user": {
    "username": "alice"
  }
}

单个普通值也想从 body 里取

如果只有一个普通参数,例如 importance: int,FastAPI 默认会把它当成查询参数。想让它从请求体里取,需要使用 Body:

from fastapi import Body

@app.post("/items/{item_id}")
async def update_item(
    item_id: int,
    importance: int = Body(gt=0)
):
    return {"item_id": item_id, "importance": importance}

请求体:

{
  "importance": 5
}

三种参数可以混用

@app.put("/items/{item_id}")
async def update_item(
    item_id: int,
    q: str | None = None,
    item: Item
):
    return {"item_id": item_id, "q": q, "item": item}

请求示例:

PUT /items/123?q=hello
Content-Type: application/json

{
  "name": "书",
  "price": 39
}

实际开发中,路径参数适合表示资源标识,比如用户 ID、文章 ID;查询参数适合表示过滤、分页、排序;请求体参数适合表示需要提交的复杂数据,比如创建用户、更新文章、提交表单。