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:
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 define a
Item
Pydantic model to represent our request body - FastAPI automatically validates incoming requests against this model
- If validation passes, the
create_item
function receives the parseditem
object
An example request to this endpoint would look like:
POST /items/
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 5.5
}
The response would be:
{
"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:
@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:
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:
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:
- Using
Field()
parameters with individual examples - 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:
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:
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:
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:
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:
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:
- Basic request body handling with Pydantic models
- Combining path parameters with request bodies
- Adding examples to your API documentation
- Using the
Body
class for enhanced control - Working with form data and file uploads
- 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
- Create an API endpoint that accepts a list of items in a shopping cart
- Build a user profile update API that validates email and phone number formats
- Implement a file upload system that validates file types and sizes
- Create a nested model for handling complex JSON structures
- 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! :)