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 Itemobject 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:
- namemust be between 1-50 characters and capitalized
- pricemust be greater than 0
- is_offerdefaults to- Falseif not provided
- tagsaccepts a list of strings
- inventoryis 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_idis a path parameter
- itemis the request body
- qis 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
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!