Skip to main content

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 than
  • ge: Greater than or equal
  • lt: Less than
  • le: Less than or equal
  • min_length: Minimum string length
  • max_length: Maximum string length
  • regex: 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}
@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

  1. Use Type Hints: Always specify parameter types for automatic validation
  2. Validate Early: Use Pydantic models and Field validators to catch errors early
  3. Clear Parameter Names: Use descriptive names that indicate purpose
  4. Document Parameters: Add descriptions using Field or Query/Path/Header
  5. Default Values: Provide sensible defaults for optional parameters
  6. Limit Payload Size: Set reasonable limits for file uploads and request bodies
  7. Use Enums: Restrict values to valid options when applicable
  8. Handle Files Async: Use async operations for file handling
  9. Sanitize Input: Validate and sanitize all user input
  10. 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.