Defining Request Bodies with Pydantic¶
🎯 What You’ll Learn¶
- What request bodies are and when to use them
- How to define request schemas using Pydantic models
- How FastAPI automatically validates and documents request bodies
📦 What Is a Request Body?¶
A request body is the part of an HTTP request that contains data sent by the client — typically in JSON format. It’s used in methods like POST, PUT, and PATCH to create or update resources.
Example:
{
"title": "Buy milk",
"completed": false
}
This is different from:
- Path parameters: part of the URL
- Query parameters: part of the URL after
?
🧠 Why Use Pydantic?¶
FastAPI uses Pydantic to define and validate request bodies. Pydantic models:
- Define the expected structure of incoming data
- Automatically validate types and required fields
- Generate clear error messages if validation fails
- Power the OpenAPI docs (Swagger UI and ReDoc)
🛠️ Step-by-Step: Defining a Request Body¶
Let’s build a simple API that accepts a task object via POST.
📄 main.py¶
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Task(BaseModel):
title: str
completed: bool
@app.post("/tasks")
def create_task(task: Task):
return {"message": "Task created", "task": task}
🔍 Breakdown¶
Taskis a Pydantic model that defines the expected JSON structure.- FastAPI automatically:
- Parses the incoming JSON
- Validates the types (
str,bool) - Injects the validated
taskobject into the function
🧪 Try It Out¶
Run the server:
uvicorn main:app --reload
Then visit http://127.0.0.1:8000/docs and test the /tasks endpoint.
Example request body:
{
"title": "Buy milk",
"completed": false
}
Response:
{
"message": "Task created",
"task": {
"title": "Buy milk",
"completed": false
}
}
❌ What Happens If Validation Fails?¶
Try sending this:
{
"title": 123,
"completed": "nope"
}
FastAPI returns a 422 Unprocessable Entity error:
{
"detail": [
{
"loc": ["body", "title"],
"msg": "str type expected",
"type": "type_error.str"
},
{
"loc": ["body", "completed"],
"msg": "value could not be parsed to a boolean",
"type": "type_error.bool"
}
]
}
This is incredibly helpful for debugging and frontend integration.
🧩 Optional Fields and Defaults¶
You can make fields optional or give them default values:
class Task(BaseModel):
title: str
completed: bool = False
description: str | None = None
completeddefaults toFalseif not provideddescriptionis optional (Nonemeans it can be missing)
🧠 Practice Challenge¶
Create a model called User with:
username: stringemail: stringis_active: boolean (defaultTrue)bio: optional string
Then create a POST /users endpoint that accepts this model and returns the user data.