Request Handling
Master FastAPI's comprehensive request handling capabilities, from simple query parameters to complex nested data structures with automatic validation.
Path Parameters
Basic Path Parameters
Extract values from URL paths with automatic type validation:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id}
@app.get("/users/{user_id}/posts/{post_id}")
def read_user_post(user_id: int, post_id: int):
return {"user_id": user_id, "post_id": post_id}
Path Parameter Validation
Use Pydantic's Field for additional validation:
from fastapi import Path
@app.get("/items/{item_id}")
def read_item(
item_id: int = Path(..., title="Item ID", ge=1, le=1000)
):
return {"item_id": item_id}
Validation options:
gt: Greater thange: Greater than or equallt: Less thanle: Less than or equalmin_length: Minimum string lengthmax_length: Maximum string lengthregex: Regular expression pattern
String Path Parameters
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
# Matches paths like /files/home/user/myfile.txt
return {"file_path": file_path}
Enum Path Parameters
Restrict values to predefined options:
from enum import Enum
class Category(str, Enum):
electronics = "electronics"
clothing = "clothing"
food = "food"
@app.get("/categories/{category}")
def get_category(category: Category):
return {"category": category.value}
Query Parameters
Basic Query Parameters
from typing import Optional
@app.get("/items")
def read_items(
skip: int = 0,
limit: int = 10,
q: Optional[str] = None
):
return {"skip": skip, "limit": limit, "q": q}
# GET /items?skip=20&limit=50&q=search
Required Query Parameters
Omit default value to make parameter required:
@app.get("/items")
def read_items(item_id: int, quantity: int):
# Both item_id and quantity are required
return {"item_id": item_id, "quantity": quantity}
Query Parameter Validation
from fastapi import Query
@app.get("/items")
def read_items(
q: Optional[str] = Query(
None,
min_length=3,
max_length=50,
regex="^[a-zA-Z0-9]+$",
title="Query string",
description="Search query for items"
)
):
return {"q": q}
List Query Parameters
Accept multiple values for the same parameter:
from typing import List
@app.get("/items")
def read_items(ids: List[int] = Query([])):
return {"ids": ids}
# GET /items?ids=1&ids=2&ids=3
Boolean Query Parameters
@app.get("/items")
def read_items(active: bool = True):
return {"active": active}
# GET /items?active=true
# GET /items?active=false
# GET /items (uses default: true)
Request Body
Basic Request Body
Use Pydantic models for structured data:
from pydantic import BaseModel
from typing import Optional
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
@app.post("/items")
def create_item(item: Item):
return item
Request Body with Validation
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
description: Optional[str] = Field(None, max_length=500)
price: float = Field(..., gt=0, description="Price must be positive")
tax: Optional[float] = Field(None, ge=0)
quantity: int = Field(default=1, ge=1, le=1000)
@app.post("/items")
def create_item(item: Item):
return item
Nested Models
from typing import List
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
images: List[Image] = []
@app.post("/items")
def create_item(item: Item):
return item
# Request body:
# {
# "name": "Phone",
# "price": 599.99,
# "images": [
# {"url": "http://example.com/img1.jpg", "name": "Front view"},
# {"url": "http://example.com/img2.jpg", "name": "Back view"}
# ]
# }
Multiple Body Parameters
class Item(BaseModel):
name: str
price: float
class User(BaseModel):
username: str
email: str
@app.post("/purchase")
def purchase_item(item: Item, user: User, quantity: int = Body(...)):
return {"item": item, "user": user, "quantity": quantity}
# Request body:
# {
# "item": {"name": "Phone", "price": 599.99},
# "user": {"username": "john", "email": "john@example.com"},
# "quantity": 2
# }
Singular Body Parameter
Use Body() to embed a single value:
from fastapi import Body
@app.post("/items/{item_id}")
def update_item(
item_id: int,
item: Item,
importance: int = Body(...)
):
return {"item_id": item_id, "item": item, "importance": importance}
Headers
Reading Headers
from fastapi import Header
from typing import Optional
@app.get("/items")
def read_items(
user_agent: Optional[str] = Header(None),
x_token: Optional[str] = Header(None)
):
return {"User-Agent": user_agent, "X-Token": x_token}
Note: Header names are automatically converted from hyphen-case to underscore_case.
Header Validation
@app.get("/items")
def read_items(
x_api_key: str = Header(..., min_length=32, max_length=32)
):
return {"x_api_key": x_api_key}
Duplicate Headers
from typing import List
@app.get("/items")
def read_items(x_token: List[str] = Header(None)):
return {"X-Token values": x_token}
Cookies
Reading Cookies
from fastapi import Cookie
@app.get("/items")
def read_items(
session_id: Optional[str] = Cookie(None),
ads_id: Optional[str] = Cookie(None)
):
return {"session_id": session_id, "ads_id": ads_id}
Cookie Validation
@app.get("/items")
def read_items(
session_id: str = Cookie(..., min_length=32)
):
return {"session_id": session_id}
Form Data
Simple Forms
from fastapi import Form
@app.post("/login")
def login(
username: str = Form(...),
password: str = Form(...)
):
return {"username": username}
Form with Validation
@app.post("/register")
def register(
username: str = Form(..., min_length=3, max_length=20),
email: str = Form(..., regex=r"^[\w\.-]+@[\w\.-]+\.\w+$"),
password: str = Form(..., min_length=8),
age: int = Form(..., ge=18)
):
return {"username": username, "email": email}
File Uploads
Single File Upload
from fastapi import File, UploadFile
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
contents = await file.read()
return {
"filename": file.filename,
"content_type": file.content_type,
"size": len(contents)
}
Multiple Files
from typing import List
@app.post("/upload-multiple")
async def upload_multiple(files: List[UploadFile] = File(...)):
return [
{"filename": file.filename, "size": len(await file.read())}
for file in files
]
File with Additional Form Data
@app.post("/upload")
async def upload_file(
file: UploadFile = File(...),
title: str = Form(...),
description: str = Form(None)
):
return {
"filename": file.filename,
"title": title,
"description": description
}
Large File Handling
@app.post("/upload-large")
async def upload_large_file(file: UploadFile = File(...)):
# Read file in chunks
chunk_size = 1024 * 1024 # 1 MB
async with aiofiles.open(f"uploads/{file.filename}", 'wb') as f:
while True:
chunk = await file.read(chunk_size)
if not chunk:
break
await f.write(chunk)
return {"filename": file.filename, "status": "uploaded"}
Request Object
Direct Request Access
from fastapi import Request
@app.get("/request-info")
async def request_info(request: Request):
return {
"method": request.method,
"url": str(request.url),
"headers": dict(request.headers),
"query_params": dict(request.query_params),
"path_params": dict(request.path_params),
"client": request.client.host if request.client else None
}
Reading Request Body
@app.post("/raw-body")
async def read_raw_body(request: Request):
body = await request.body()
return {"raw_body": body.decode()}
JSON Body Parsing
@app.post("/json-body")
async def read_json_body(request: Request):
json_body = await request.json()
return {"json": json_body}
Data Validation
Custom Validators
from pydantic import BaseModel, validator
class Item(BaseModel):
name: str
price: float
discount_price: Optional[float] = None
@validator('discount_price')
def discount_must_be_lower(cls, v, values):
if v is not None and 'price' in values and v >= values['price']:
raise ValueError('discount_price must be lower than price')
return v
@app.post("/items")
def create_item(item: Item):
return item
Root Validators
from pydantic import root_validator
class DateRange(BaseModel):
start_date: str
end_date: str
@root_validator
def check_dates(cls, values):
start = values.get('start_date')
end = values.get('end_date')
if start and end and start > end:
raise ValueError('start_date must be before end_date')
return values
Request Parsing Options
Custom JSON Parsing
from fastapi.encoders import jsonable_encoder
@app.post("/items")
def create_item(item: Item):
json_compatible = jsonable_encoder(item)
return json_compatible
Handle Extra Fields
class Item(BaseModel):
name: str
price: float
class Config:
extra = "forbid" # Raise error on extra fields
# extra = "allow" # Accept extra fields
# extra = "ignore" # Ignore extra fields (default)
@app.post("/items")
def create_item(item: Item):
return item
Advanced Request Handling
Handling Different Content Types
from fastapi import Request
@app.post("/data")
async def receive_data(request: Request):
content_type = request.headers.get("content-type")
if content_type == "application/json":
data = await request.json()
elif content_type == "application/x-www-form-urlencoded":
data = await request.form()
elif content_type == "text/plain":
data = await request.body()
else:
return {"error": "Unsupported content type"}
return {"data": data, "content_type": content_type}
Request Size Limits
from fastapi import HTTPException
@app.post("/upload")
async def upload(request: Request):
content_length = request.headers.get("content-length")
if content_length:
content_length = int(content_length)
max_size = 10 * 1024 * 1024 # 10 MB
if content_length > max_size:
raise HTTPException(
status_code=413,
detail="File too large"
)
body = await request.body()
return {"size": len(body)}
Request Middleware
@app.middleware("http")
async def log_requests(request: Request, call_next):
print(f"Request: {request.method} {request.url}")
response = await call_next(request)
print(f"Response status: {response.status_code}")
return response
Best Practices
- Use Type Hints: Always specify parameter types for automatic validation
- Validate Early: Use Pydantic models and Field validators to catch errors early
- Clear Parameter Names: Use descriptive names that indicate purpose
- Document Parameters: Add descriptions using Field or Query/Path/Header
- Default Values: Provide sensible defaults for optional parameters
- Limit Payload Size: Set reasonable limits for file uploads and request bodies
- Use Enums: Restrict values to valid options when applicable
- Handle Files Async: Use async operations for file handling
- Sanitize Input: Validate and sanitize all user input
- Error Messages: Provide clear, helpful error messages for validation failures
Request handling in FastAPI is powerful and flexible while maintaining type safety and automatic validation. Master these patterns to build robust, secure APIs.