Skip to main content

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:

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

json
{
"name": "Smartphone",
"description": "Latest model with high-end features",
"price": 799.99,
"tax": 80.00
}

FastAPI will:

  1. Read the request body as JSON
  2. Convert the corresponding types (like strings, floats)
  3. Validate the data (required fields, correct types)
  4. Create the Item object with the attributes
  5. Pass this object to your function

The response would be:

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

  1. Reads the request body: Gets the raw JSON (or other formats) from the HTTP request
  2. Parses the JSON: Converts it into Python dictionaries, lists, etc.
  3. Validates data types: Ensures values match the types you defined
  4. 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:

python
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 capitalized
  • price must be greater than 0
  • is_offer defaults to False if not provided
  • tags accepts a list of strings
  • inventory is optional

Multiple Models and Nested Request Bodies

For more complex data structures, you can nest models within each other:

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

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

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, 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 parameter
  • item is the request body
  • q is an optional query parameter

Request Body in Form Data

Sometimes you need to receive form data instead of JSON:

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

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

python
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

  1. Create a blog post API that accepts a request body with title, content, tags, and author information
  2. Implement a product review system with nested models for product details and user reviews
  3. Build an API endpoint that accepts both JSON and form data for the same operation

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)