Skip to main content

FastAPI Request Examples

In this guide, we'll explore how to work with request bodies in FastAPI, one of Python's most popular API frameworks. Understanding how to properly handle and validate incoming requests is crucial for building robust web APIs.

Introduction to Request Bodies in FastAPI

When building APIs, you often need to receive data from clients through request bodies. FastAPI makes this process straightforward while providing powerful validation features.

Request bodies typically contain JSON data sent by clients when making POST, PUT, or PATCH requests. FastAPI automatically parses these requests and converts them to Python objects, making them easy to work with.

Basic Request Body Example

Let's start with a simple example of handling 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 define a Item Pydantic model to represent our request body
  2. FastAPI automatically validates incoming requests against this model
  3. If validation passes, the create_item function receives the parsed item object

An example request to this endpoint would look like:

json
POST /items/
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 5.5
}

The response would be:

json
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 5.5,
"price_with_tax": 50.7
}

Using Request Body + Path Parameters

You can combine path parameters with request bodies:

python
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, "item": item.dict()}

This allows you to identify a specific resource with the path parameter while sending the updated data in the request body.

Multiple Parameters and Request Bodies

FastAPI can handle multiple parameters and body inputs:

python
from fastapi import FastAPI, Path, 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 = Path(..., title="The ID of the item to update"),
item: Item = None,
q: str = Query(None, description="Query string for the items to search")
):
result = {"item_id": item_id}
if q:
result.update({"q": q})
if item:
result.update({"item": item.dict()})
return result

Adding Examples to Your Models

FastAPI allows you to provide examples of valid request bodies in your API documentation:

python
from fastapi import FastAPI
from pydantic import BaseModel, Field
from typing import Dict

app = FastAPI()

class Item(BaseModel):
name: str = Field(..., example="Foo")
description: str = Field(None, example="A very nice Item")
price: float = Field(..., example=35.4)
tax: float = Field(None, example=3.2)

class Config:
schema_extra = {
"example": {
"name": "Fancy Item",
"description": "A fancy item with comprehensive description",
"price": 45.2,
"tax": 5.5,
}
}

@app.post("/items/")
async def create_item(item: Item):
return item

In this example, we provide examples in two ways:

  1. Using Field() parameters with individual examples
  2. Using the schema_extra config to provide a complete example object

These examples will appear in the OpenAPI documentation (Swagger UI).

Using Body for More Control

For more control over request body handling, you can use the Body class:

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(
...,
example={
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
),
):
results = {"item_id": item_id, "item": item}
return results

The Body() parameter allows you to provide examples directly in your endpoint definition.

Multiple Examples with FastAPI

You can provide multiple examples for better documentation:

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(
...,
examples={
"normal": {
"summary": "A normal example",
"description": "A normal item with all the details",
"value": {
"name": "Foo",
"description": "A very nice Item",
"price": 35.4,
"tax": 3.2,
},
},
"converted": {
"summary": "An example with converted price to USD",
"description": "Assumes the item price was in EUR and converts it to USD",
"value": {
"name": "Bar",
"price": 45.1,
"tax": None,
},
},
"invalid": {
"summary": "Invalid data is rejected with an error",
"value": {
"name": "Baz",
"price": "invalid price",
},
},
},
),
):
results = {"item_id": item_id, "item": item}
return results

This approach lets you showcase different scenarios in your API documentation.

Handling Form Data

Sometimes you need to handle traditional form submissions 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}

Notice that we use Form() instead of directly declaring the parameters. This tells FastAPI to look for form data instead of JSON.

Working with Files and Uploads

FastAPI makes it easy to handle file uploads:

python
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/files/")
async def create_file(file: bytes = File(...)):
return {"file_size": len(file)}

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
contents = await file.read()
return {"filename": file.filename, "content_type": file.content_type, "file_size": len(contents)}

The UploadFile class provides additional features like filename, content_type, and methods to handle the file without loading it entirely into memory.

Real-World Application: User Registration API

Let's build a more complex example for user registration with validation:

python
from fastapi import FastAPI, HTTPException, Body
from pydantic import BaseModel, Field, EmailStr, validator
from typing import Optional
import re

app = FastAPI()

class UserRegistration(BaseModel):
username: str = Field(..., min_length=3, max_length=50)
email: EmailStr
password: str = Field(..., min_length=8)
full_name: Optional[str] = Field(None, max_length=100)

@validator('password')
def password_strength(cls, v):
if not re.search(r'[A-Z]', v):
raise ValueError('Password must contain an uppercase letter')
if not re.search(r'[a-z]', v):
raise ValueError('Password must contain a lowercase letter')
if not re.search(r'\d', v):
raise ValueError('Password must contain a number')
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', v):
raise ValueError('Password must contain a special character')
return v

class Config:
schema_extra = {
"example": {
"username": "johndoe",
"email": "[email protected]",
"password": "SecurePass1!",
"full_name": "John Doe"
}
}

@app.post("/users/register")
async def register_user(
user: UserRegistration = Body(
...,
examples={
"valid": {
"summary": "Valid registration",
"description": "A valid user registration example",
"value": {
"username": "johndoe",
"email": "[email protected]",
"password": "SecurePass1!",
"full_name": "John Doe"
}
},
"invalid_password": {
"summary": "Invalid password",
"description": "Password doesn't meet complexity requirements",
"value": {
"username": "johndoe",
"email": "[email protected]",
"password": "simple",
"full_name": "John Doe"
}
}
}
)
):
# In a real app, you would hash the password and store in a database
# This is just a simplified example
return {
"message": "User registered successfully",
"username": user.username,
"email": user.email
}

This example includes:

  • Strong validation rules for user data
  • Custom validator for password strength
  • Multiple examples for documentation
  • Clear error messages for invalid inputs

Summary

In this guide, we've covered:

  1. Basic request body handling with Pydantic models
  2. Combining path parameters with request bodies
  3. Adding examples to your API documentation
  4. Using the Body class for enhanced control
  5. Working with form data and file uploads
  6. A real-world example with advanced validation

FastAPI's request handling system gives you powerful tools to create robust APIs with minimal code. By leveraging Pydantic models, you get automatic validation, documentation, and type conversion for your API endpoints.

Additional Resources

Exercises for Practice

  1. Create an API endpoint that accepts a list of items in a shopping cart
  2. Build a user profile update API that validates email and phone number formats
  3. Implement a file upload system that validates file types and sizes
  4. Create a nested model for handling complex JSON structures
  5. Implement a search API that takes multiple query parameters and a request body


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