FastAPI Request Body
Introduction
When building APIs, you often need to receive and process data sent by clients. This data might come from forms, JSON payloads, or other formats. In FastAPI, handling this incoming data is made simple through request bodies.
A request body is data sent by the client to your API in the body of the request. This is different from path parameters, query parameters, or headers. Request bodies are commonly used in POST, PUT, and PATCH operations where you need to send complex data structures to create or update resources.
In this tutorial, we'll explore how FastAPI makes it easy to work with request bodies using Pydantic models for data validation and conversion.
Prerequisites
To follow along, you should have:
- Basic knowledge of Python
- FastAPI installed (
pip install fastapi uvicorn
) - Understanding of HTTP methods (GET, POST, PUT, DELETE)
Understanding Request Bodies in FastAPI
In FastAPI, request bodies are typically defined using Pydantic models, which provide:
- Automatic data validation
- Data conversion (parsing)
- JSON Schema generation
- Automatic documentation
Let's see how to use request bodies in your FastAPI applications.
Basic Request Body Example
Here's a simple example of how to define and use a request body:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
@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.update({"price_with_tax": price_with_tax})
return item_dict
In this example:
- We create a Pydantic model called
Item
with various fields - We declare a POST endpoint that receives an
Item
object - FastAPI automatically handles:
- Reading the request body as JSON
- Converting the corresponding types
- Validating the data
- Providing the received data in the
item
parameter
How it works
When a client sends a request like:
{
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00
}
FastAPI will:
- Read the JSON request body
- Convert the types as needed (strings to strings, numbers to floats, etc.)
- Validate the data (e.g., ensuring required fields are present)
- Provide the data in the
item
parameter as an instance of theItem
class
The response would be:
{
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00,
"price_with_tax": 1149.99
}
Request Body + Path Parameters
You can combine path parameters with request bodies:
from fastapi import FastAPI
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):
return {"item_id": item_id, **item.dict()}
FastAPI knows that the function parameters that match path parameters should be taken from the path, and function parameters that are declared with Pydantic models should be taken from the request body.
Request Body + Path + Query Parameters
You can also declare body, path, and query parameters all at the same time:
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, description="Additional query parameter")
):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result
FastAPI will recognize:
item_id
as a path parameterq
as a query parameteritem
as a request body
Multiple Body Parameters
You can also declare multiple body parameters:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
class User(BaseModel):
username: str
full_name: str = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
results = {"item_id": item_id, "item": item, "user": user}
return results
In this case, FastAPI will expect a JSON body with a key item
containing the item's data and a key user
containing the user's data:
{
"item": {
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00
},
"user": {
"username": "johndoe",
"full_name": "John Doe"
}
}
Using the Body Parameter
For more control over how FastAPI treats request body parameters, you can use the Body()
function:
from fastapi import FastAPI, Body
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
class User(BaseModel):
username: str
full_name: str = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item,
user: User,
importance: int = Body(...)
):
results = {
"item_id": item_id,
"item": item,
"user": user,
"importance": importance
}
return results
In this example, the expected JSON body would be:
{
"item": {
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00
},
"user": {
"username": "johndoe",
"full_name": "John Doe"
},
"importance": 5
}
Singular Values in Body
If you want a parameter to be included in the body directly rather than inside a nested JSON object, you can use Body()
with the embed
parameter:
from fastapi import FastAPI, Body
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 = Body(..., embed=True)
):
results = {"item_id": item_id, "item": item}
return results
With embed=True
, FastAPI will expect a JSON body like:
{
"item": {
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00
}
}
Without embed=True
, it would expect:
{
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00
}
Real-world Example: User Registration API
Let's create a more practical example—a user registration endpoint for an application:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr, validator
from typing import Optional
import re
app = FastAPI()
class UserRegistration(BaseModel):
username: str
email: EmailStr
password: str
full_name: Optional[str] = None
age: Optional[int] = None
@validator('username')
def username_must_be_valid(cls, v):
if not re.match(r'^[a-zA-Z0-9_-]{3,16}$', v):
raise ValueError('Username must be 3-16 characters and contain only letters, numbers, underscores and hyphens')
return v
@validator('password')
def password_must_be_strong(cls, v):
if len(v) < 8:
raise ValueError('Password must be at least 8 characters')
if not any(c.isupper() for c in v):
raise ValueError('Password must contain at least one uppercase letter')
if not any(c.islower() for c in v):
raise ValueError('Password must contain at least one lowercase letter')
if not any(c.isdigit() for c in v):
raise ValueError('Password must contain at least one number')
return v
@app.post("/register/", status_code=201)
async def register_user(user: UserRegistration):
# In a real application, you would:
# 1. Check if username or email already exists
# 2. Hash the password
# 3. Store user in database
# For this example, we'll just return a success message
return {
"message": "User registered successfully",
"username": user.username,
"email": user.email,
# Don't return sensitive information like passwords in real apps!
}
This example includes:
- Email validation with
EmailStr
(requires installingpip install email-validator
) - Custom validators for username and password rules
- Proper HTTP status code (201 Created) for successful registration
- Comments indicating where you would add database operations in a real application
Advanced Techniques with Request Bodies
Field Descriptions and Examples
You can enhance your Pydantic models with additional information using Field()
:
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Optional
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., example="Laptop", description="Name of the item")
description: Optional[str] = Field(None, example="A powerful laptop for development", description="Detailed description of the item")
price: float = Field(..., gt=0, example=999.99, description="Price of the item in USD")
tax: Optional[float] = Field(None, ge=0, example=150.0, description="Tax amount in USD")
@app.post("/items/")
async def create_item(item: Item):
return item
This enhances your automatic API documentation with descriptions and examples.
Nested Models
For complex data structures, you can nest Pydantic models:
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
class Image(BaseModel):
url: str
name: str
class Tag(BaseModel):
name: str
description: Optional[str] = None
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
tags: List[Tag] = []
images: Optional[List[Image]] = None
@app.post("/items/")
async def create_item(item: Item):
return item
This allows you to handle nested JSON objects in your request body:
{
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00,
"tags": [
{"name": "electronics", "description": "Electronic devices"},
{"name": "computers", "description": "Computing devices"}
],
"images": [
{"url": "https://example.com/laptop/image1.jpg", "name": "Front view"},
{"url": "https://example.com/laptop/image2.jpg", "name": "Side view"}
]
}
Summary
In this tutorial, you've learned:
- How to use Pydantic models to declare request bodies in FastAPI
- How to combine request bodies with path and query parameters
- How to handle multiple body parameters
- How to use embedded and non-embedded body parameters
- How to create practical APIs with request validation
- Advanced techniques like field descriptions, examples, and nested models
Request bodies are a fundamental part of API development, allowing you to receive structured data from clients. FastAPI's integration with Pydantic makes it easy to validate, convert, and document these inputs, resulting in robust and well-documented APIs.
Additional Resources
Exercises
- Create an API endpoint that accepts a blog post with title, content, author information, and tags
- Build a product inventory API that accepts product details including name, SKU, price, categories, and stock information
- Implement an e-commerce order endpoint that accepts customer details, shipping address, and a list of ordered items with quantities
- Create a user profile update API that validates email format, username requirements, and optional information
By practicing these exercises, you'll gain confidence in working with request bodies in FastAPI.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)