FastAPI Request Parsing
When building web APIs, one of the most fundamental operations is extracting and parsing data sent by clients in their HTTP requests. FastAPI provides elegant and powerful tools to handle this task with minimal boilerplate code while ensuring proper validation.
Introduction
Request parsing is the process of extracting data from incoming HTTP requests and converting it into Python objects that your application can work with. FastAPI excels at this by leveraging Python's type annotations to:
- Extract data from various parts of the request
- Convert string data to appropriate Python types
- Validate the data meets specified requirements
- Generate automatic API documentation
In this tutorial, we'll explore the different ways FastAPI allows you to parse request data, with practical examples for each approach.
Query Parameters
Query parameters are the most common way to send data in GET requests, appearing in the URL after a question mark.
Basic Query Parameters
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
When a client makes a request to /items/?skip=20&limit=30
, FastAPI will:
- Extract the query parameters
skip
andlimit
from the URL - Convert them to integers based on the type annotations
- Pass them as function parameters to your endpoint function
- Return
{"skip": 20, "limit": 30}
Optional Query Parameters
To make a query parameter optional, use Python's Optional
type hint:
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(name: Optional[str] = None, age: Optional[int] = None):
response = {}
if name:
response["name"] = name
if age:
response["age"] = age
return response
Query Parameter Validation
FastAPI supports extensive validation through Pydantic:
from fastapi import FastAPI, Query
from typing import Optional
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Optional[str] = Query(
None,
min_length=3,
max_length=50,
regex="^[a-zA-Z0-9_-]*$",
description="Search query string"
)
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results["filter_applied"] = q
return results
Path Parameters
Path parameters are part of the URL path itself and are typically used to identify specific resources.
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
async def read_user(user_id: int):
return {"user_id": user_id}
When a client makes a request to /users/123
, FastAPI will:
- Extract
user_id
as123
from the URL path - Convert it to an integer
- Return
{"user_id": 123}
Path Parameter Validation
You can add validation using the Path
class:
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(..., title="The ID of the item", ge=1)
):
return {"item_id": item_id}
The ...
indicates the parameter is required, and ge=1
ensures the value is greater than or equal to 1.
Request Body
For POST, PUT, and PATCH requests, you'll often need to parse data from the request body. FastAPI makes this straightforward using Pydantic models.
Basic Request Body
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict["price_with_tax"] = price_with_tax
return item_dict
For a POST request to /items/
with this JSON body:
{
"name": "Laptop",
"price": 999.99,
"description": "Powerful development machine"
}
FastAPI will:
- Read the JSON request body
- Convert it to the
Item
model - Validate the data (ensuring
name
andprice
are present and of the correct types) - Return the processed data
Nested Models
You can define complex nested models for structured data:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
class Image(BaseModel):
url: str
name: str
class Item(BaseModel):
name: str
price: float
images: List[Image] = []
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
Multiple Body Parameters
You can receive multiple body parameters:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
price: float
class User(BaseModel):
username: str
email: str
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item, user: User):
return {"item": item, "user": user}
FastAPI will know which model corresponds to which part of the request body.
Form Data
To parse form data submitted from HTML forms, use the Form
class:
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}
Note: You'll need to install python-multipart
to use this feature:
pip install python-multipart
File Uploads
FastAPI makes it easy to handle file uploads:
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/upload-file/")
async def upload_file(file: UploadFile = File(...)):
contents = await file.read()
# Process the file contents
return {
"filename": file.filename,
"content_type": file.content_type,
"file_size": len(contents)
}
For multiple files:
from fastapi import FastAPI, File, UploadFile
from typing import List
app = FastAPI()
@app.post("/upload-files/")
async def upload_files(files: List[UploadFile] = File(...)):
return {"filenames": [file.filename for file in files]}
Headers and Cookies
You can access headers and cookies directly:
from fastapi import FastAPI, Header, Cookie
from typing import Optional
app = FastAPI()
@app.get("/items/")
async def read_items(
user_agent: Optional[str] = Header(None),
session_id: Optional[str] = Cookie(None)
):
return {"User-Agent": user_agent, "Session-ID": session_id}
Dependency Injection for Request Parsing
For reusable parsing logic, use dependency injection:
from fastapi import FastAPI, Depends, Query
from typing import Optional
app = FastAPI()
def pagination_params(skip: int = 0, limit: int = 100):
return {"skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(pagination_params)):
return {"params": commons}
@app.get("/users/")
async def read_users(commons: dict = Depends(pagination_params)):
return {"params": commons}
Real-World Example: Product Search API
Let's build a more comprehensive example that combines multiple request parsing techniques:
from fastapi import FastAPI, Query, Path, Depends, HTTPException
from pydantic import BaseModel
from typing import Optional, List
from enum import Enum
app = FastAPI()
# Enum for product categories
class Category(str, Enum):
electronics = "electronics"
clothing = "clothing"
food = "food"
books = "books"
# Product model
class Product(BaseModel):
id: int
name: str
price: float
description: Optional[str] = None
category: Category
# Mock database
products_db = [
Product(id=1, name="Laptop", price=999.99, description="Powerful laptop", category=Category.electronics),
Product(id=2, name="T-shirt", price=19.99, category=Category.clothing),
Product(id=3, name="Apple", price=0.99, description="Fresh fruit", category=Category.food),
]
# Dependency for pagination and filtering
def product_filter(
skip: int = 0,
limit: int = 10,
min_price: Optional[float] = None,
max_price: Optional[float] = None,
category: Optional[Category] = None
):
return {
"skip": skip,
"limit": limit,
"min_price": min_price,
"max_price": max_price,
"category": category
}
@app.get("/products/", response_model=List[Product])
async def get_products(filters: dict = Depends(product_filter)):
results = products_db
# Apply filters
if filters["category"]:
results = [p for p in results if p.category == filters["category"]]
if filters["min_price"] is not None:
results = [p for p in results if p.price >= filters["min_price"]]
if filters["max_price"] is not None:
results = [p for p in results if p.price <= filters["max_price"]]
# Apply pagination
start = filters["skip"]
end = start + filters["limit"]
return results[start:end]
@app.get("/products/{product_id}", response_model=Product)
async def get_product(product_id: int = Path(..., ge=1)):
product = next((p for p in products_db if p.id == product_id), None)
if product is None:
raise HTTPException(status_code=404, detail="Product not found")
return product
@app.post("/products/", response_model=Product)
async def create_product(product: Product):
# In a real app, we'd save to a database
products_db.append(product)
return product
This example demonstrates:
- Path parameters for specific resources
- Query parameters with validation through Depends
- Request body parsing using Pydantic models
- Enum types for constrained values
- Dependency injection for reusable filtering logic
Summary
FastAPI offers a powerful and intuitive system for parsing request data through:
- Query parameters - Data from the URL query string
- Path parameters - Data embedded in the URL path
- Request body - Data sent in the body of the request (using Pydantic models)
- Form data - Data from HTML forms
- File uploads - Handling uploaded files
- Headers and cookies - Accessing request metadata
By leveraging Python's type hints, FastAPI provides automatic validation, conversion, and documentation for all these input sources. This approach dramatically reduces boilerplate code while improving both data safety and developer experience.
Additional Resources
- FastAPI Official Documentation on Request Parameters
- Pydantic Documentation for advanced validation
- Starlette Documentation for underlying request handling details
Exercises
- Create an endpoint that accepts a list of product IDs as query parameters and returns details for those products
- Build a user registration system that validates email format and password strength
- Implement a file upload endpoint that can process CSV data and return a summary of its contents
- Create an API for a blog system that supports filtering posts by author, date range, and tags
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)