Skip to main content

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:

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

  1. We create a Pydantic model called Item with various fields
  2. We declare a POST endpoint that receives an Item object
  3. 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:

json
{
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00
}

FastAPI will:

  1. Read the JSON request body
  2. Convert the types as needed (strings to strings, numbers to floats, etc.)
  3. Validate the data (e.g., ensuring required fields are present)
  4. Provide the data in the item parameter as an instance of the Item class

The response would be:

json
{
"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:

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

python
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 parameter
  • q as a query parameter
  • item as a request body

Multiple Body Parameters

You can also declare multiple body parameters:

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

json
{
"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:

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

json
{
"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:

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

json
{
"item": {
"name": "Laptop",
"description": "A high-performance laptop",
"price": 999.99,
"tax": 150.00
}
}

Without embed=True, it would expect:

json
{
"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:

python
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 installing pip 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():

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

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

json
{
"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

  1. Create an API endpoint that accepts a blog post with title, content, author information, and tags
  2. Build a product inventory API that accepts product details including name, SKU, price, categories, and stock information
  3. Implement an e-commerce order endpoint that accepts customer details, shipping address, and a list of ordered items with quantities
  4. 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! :)