Skip to main content

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:

  1. Extract data from various parts of the request
  2. Convert string data to appropriate Python types
  3. Validate the data meets specified requirements
  4. 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

python
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:

  1. Extract the query parameters skip and limit from the URL
  2. Convert them to integers based on the type annotations
  3. Pass them as function parameters to your endpoint function
  4. Return {"skip": 20, "limit": 30}

Optional Query Parameters

To make a query parameter optional, use Python's Optional type hint:

python
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:

python
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.

python
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:

  1. Extract user_id as 123 from the URL path
  2. Convert it to an integer
  3. Return {"user_id": 123}

Path Parameter Validation

You can add validation using the Path class:

python
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

python
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:

json
{
"name": "Laptop",
"price": 999.99,
"description": "Powerful development machine"
}

FastAPI will:

  1. Read the JSON request body
  2. Convert it to the Item model
  3. Validate the data (ensuring name and price are present and of the correct types)
  4. Return the processed data

Nested Models

You can define complex nested models for structured data:

python
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:

python
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:

python
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:

bash
pip install python-multipart

File Uploads

FastAPI makes it easy to handle file uploads:

python
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:

python
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:

python
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:

python
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:

python
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:

  1. Path parameters for specific resources
  2. Query parameters with validation through Depends
  3. Request body parsing using Pydantic models
  4. Enum types for constrained values
  5. 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

Exercises

  1. Create an endpoint that accepts a list of product IDs as query parameters and returns details for those products
  2. Build a user registration system that validates email format and password strength
  3. Implement a file upload endpoint that can process CSV data and return a summary of its contents
  4. 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! :)