FastAPI Request Body
In modern web applications, data is frequently sent from clients to servers using request bodies. When building APIs with FastAPI, understanding how to properly handle and validate this incoming data is essential. This guide will walk you through working with request bodies in FastAPI.
Introduction to Request Bodies
A request body is data sent by the client (typically a browser or another application) to your API. Unlike query parameters or path parameters that appear in the URL, request bodies are included in the HTTP request's body section. They're commonly used for:
- Creating new resources (POST requests)
- Updating existing resources (PUT, PATCH requests)
- Sending complex data structures
- Uploading files
FastAPI makes handling request bodies straightforward by leveraging Pydantic models for automatic validation and type conversion.
Creating Your First Request Body
Let's start with a basic example of how to define and use a request body in FastAPI:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# Define a data model for the request body
class Item(BaseModel):
name: str
description: str = None # Optional field with default value None
price: float
tax: float = None
@app.post("/items/")
async def create_item(item: Item):
# FastAPI automatically deserializes the JSON to your Pydantic model
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
When you make a POST request to /items/
with a JSON body like:
{
"name": "Smartphone",
"description": "Latest model with high-end features",
"price": 799.99,
"tax": 80.00
}
FastAPI will:
- Read the request body as JSON
- Convert the corresponding types (like strings, floats)
- Validate the data (required fields, correct types)
- Create the
Item
object with the attributes - Pass this object to your function
The response would be:
{
"name": "Smartphone",
"description": "Latest model with high-end features",
"price": 799.99,
"tax": 80.00,
"price_with_tax": 879.99
}
How FastAPI Processes Request Bodies
Under the hood, FastAPI performs several steps when handling request bodies:
- Reads the request body: Gets the raw JSON (or other formats) from the HTTP request
- Parses the JSON: Converts it into Python dictionaries, lists, etc.
- Validates data types: Ensures values match the types you defined
- Creates the model instance: Populates a Pydantic model with the validated data
This process happens automatically and provides built-in error handling. If the request data doesn't match your model, FastAPI returns a detailed error response to the client.
Advanced Request Body Validation
Pydantic models in FastAPI offer powerful validation capabilities:
from fastapi import FastAPI
from pydantic import BaseModel, Field, validator
from typing import List, Optional
app = FastAPI()
class Product(BaseModel):
name: str = Field(..., min_length=1, max_length=50)
price: float = Field(..., gt=0)
is_offer: bool = False
tags: List[str] = []
inventory: Optional[int] = None
# Custom validator
@validator('name')
def name_must_be_capitalized(cls, v):
if not v[0].isupper():
raise ValueError('Product name must be capitalized')
return v
@app.post("/products/")
async def create_product(product: Product):
return {"product": product, "message": "Product created successfully"}
With this model:
name
must be between 1-50 characters and capitalizedprice
must be greater than 0is_offer
defaults toFalse
if not providedtags
accepts a list of stringsinventory
is optional
Multiple Models and Nested Request Bodies
For more complex data structures, you can nest models within each other:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
app = FastAPI()
class Address(BaseModel):
street: str
city: str
postal_code: str
country: str
class User(BaseModel):
username: str
email: str
full_name: str = None
addresses: List[Address]
@app.post("/users/")
async def create_user(user: User):
return user
This endpoint accepts complex nested data like:
{
"username": "johndoe",
"email": "[email protected]",
"full_name": "John Doe",
"addresses": [
{
"street": "123 Main St",
"city": "Anytown",
"postal_code": "12345",
"country": "USA"
},
{
"street": "456 Office Blvd",
"city": "Workville",
"postal_code": "67890",
"country": "USA"
}
]
}
Request Body + Path Parameters
You can combine request bodies with path parameters and query parameters:
from fastapi import FastAPI, Query
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item,
q: str = Query(None, max_length=50)
):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result
In this example:
item_id
is a path parameteritem
is the request bodyq
is an optional query parameter
Request Body in Form Data
Sometimes you need to receive form data instead of JSON:
from fastapi import FastAPI, Form
app = FastAPI()
@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}
This endpoint expects form data with fields "username" and "password". Note that you'll need to install python-multipart
to use Form
.
File Uploads with Request Body
You can combine form fields with file uploads:
from fastapi import FastAPI, File, UploadFile, Form
app = FastAPI()
@app.post("/files/")
async def create_file(
file: UploadFile = File(...),
description: str = Form(None),
category: str = Form(...)
):
contents = await file.read()
return {
"filename": file.filename,
"content_type": file.content_type,
"description": description,
"category": category,
"file_size": len(contents)
}
Real-World Example: User Registration API
Here's a comprehensive example of a user registration endpoint:
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel, EmailStr, Field, validator
from typing import Optional
import re
app = FastAPI()
class UserRegistration(BaseModel):
username: str = Field(..., min_length=3, max_length=30)
email: EmailStr
password: str = Field(..., min_length=8)
confirm_password: str
full_name: Optional[str] = None
terms_accepted: bool = Field(...)
@validator('username')
def username_alphanumeric(cls, v):
if not re.match(r'^[a-zA-Z0-9_-]+$', v):
raise ValueError('Username must be alphanumeric')
return v
@validator('confirm_password')
def passwords_match(cls, v, values, **kwargs):
if 'password' in values and v != values['password']:
raise ValueError('Passwords do not match')
return v
@validator('terms_accepted')
def terms_must_be_accepted(cls, v):
if not v:
raise ValueError('Terms and conditions must be accepted')
return v
@app.post("/register/", status_code=status.HTTP_201_CREATED)
async def register_user(user: UserRegistration):
# In a real application, you would hash the password and save to database
# For this example, we just simulate checking if the username exists
if user.username == "admin":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already registered"
)
# Return a success message without exposing the password
return {
"message": "User registered successfully",
"username": user.username,
"email": user.email,
"full_name": user.full_name
}
This registration API demonstrates:
- Complex validations using Pydantic
- Custom validators for field validation
- Field restrictions (min_length, etc.)
- Proper error handling with HTTP status codes
- Security consideration (not returning the password in the response)
Summary
Request bodies in FastAPI provide a powerful mechanism for receiving and validating data from clients. By leveraging Pydantic models, you get automatic validation, documentation, and clean, type-annotated code. This approach makes your API more robust, maintainable, and self-documenting.
Key points to remember:
- Use Pydantic models to define your request bodies
- Take advantage of validation and default values
- Combine request bodies with path parameters and query parameters as needed
- Utilize nested models for complex data structures
- Implement custom validators for domain-specific validation rules
Further Learning
Exercises
- Create a blog post API that accepts a request body with title, content, tags, and author information
- Implement a product review system with nested models for product details and user reviews
- Build an API endpoint that accepts both JSON and form data for the same operation
Additional Resources
- FastAPI Official Documentation on Request Bodies
- Pydantic Documentation
- HTTP Request Methods on MDN
- JSON Schema for understanding the validation system behind FastAPI
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)