Path, Query, and Request Body
Introduction
FastAPI reads HTTP data from path segments, query strings, and JSON bodies—and validates types before your function executes. This chapter covers Path, Query, and Pydantic models as request bodies, the core of every REST endpoint.
Prerequisites
- Your First FastAPI Application
- Basic HTTP concepts (GET, POST, URL query strings)
Path Parameters
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
def read_user(user_id: int):
return {"user_id": user_id}URL /users/42 → user_id == 42.
Invalid type /users/abc → 422 Unprocessable Entity with JSON detail.
Path Validation
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(
item_id: int = Path(..., ge=1, le=1000, description="Primary key"),
):
return {"item_id": item_id}Code explanation:
Path(...)adds constraints and OpenAPI documentation...means required (Ellipsis)
Order rule: path params have no defaults; put them before query params with defaults.
Query Parameters
Function parameters without path placeholders become query params:
@app.get("/items")
def list_items(skip: int = 0, limit: int = 10, q: str | None = None):
return {"skip": skip, "limit": limit, "q": q}Request: GET /items?skip=20&limit=5&q=phone
Query Validation
from fastapi import Query
@app.get("/search")
def search(
q: str = Query(..., min_length=1, max_length=100),
page: int = Query(1, ge=1),
):
return {"q": q, "page": page}Code explanation:
Query(...)marks required query param with validationq: str = Nonealone makes an optional query param without extra metadata
Boolean query:
@app.get("/items/")
def list_items(active: bool = True):
return {"active_only": active}/items/?active=false parses correctly.
Request Body (JSON)
Define a Pydantic model (full chapter next):
from pydantic import BaseModel, Field
class ItemCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0)
tags: list[str] = []
@app.post("/items")
def create_item(item: ItemCreate):
return {"item": item.model_dump(), "saved": True}Test:
curl -X POST http://127.0.0.1:8000/items \
-H "Content-Type: application/json" \
-d '{"name":"Phone","price":699.99,"tags":["electronics"]}'Code explanation:
- FastAPI reads JSON body and validates against
ItemCreate - Validation errors return 422 with field locations
Multiple Parameters Together
Typical pattern: path IDs + optional query filters + JSON body on PUT/PATCH.
Form Data Preview
HTML forms use Form() (not JSON):
from fastapi import Form
@app.post("/login-form")
def login_form(username: str = Form(), password: str = Form()):
return {"username": username}Requires pip install python-multipart. File uploads expand in file uploads chapter.
422 Error Response Shape
Invalid body example response:
{
"detail": [
{
"type": "greater_than",
"loc": ["body", "price"],
"msg": "Input should be greater than 0",
"input": -1
}
]
}Use detail in client error handling and tests.
FAQ
Parameter in path and query same name?
Avoid—use distinct names. Path takes precedence in routing.
Optional body on PUT?
Use ItemUpdate | None = None or separate schemas—see Pydantic chapter.
Array query params?
tags: list[str] = Query() → ?tags=a&tags=b.
POST data not in body?
Missing Content-Type: application/json or wrong curl -d format.
FastAPI vs Flask request.args?
Flask uses request.args manually; FastAPI declares params in function signature.
Embed multiple body models?
Advanced Body(..., embed=True)—rare; prefer single nested model.