Request Bodies with Pydantic
Use Pydantic BaseModel to define and validate POST request bodies.
So far we've only done GET requests. How do I send data to the API — like creating a new user?
You use a POST request with a request body. In FastAPI, you define the shape of that body using a Pydantic model — a class that inherits from BaseModel:
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class Task(BaseModel):
title: str
description: str
priority: int = 1
@app.post("/tasks")
def create_task(task: Task):
return {"id": 1, "task": task.model_dump()}
When someone sends a POST to /tasks with JSON like {"title": "Buy milk", "description": "2%"}, FastAPI automatically parses it into a Task object. The priority field has a default of 1, so it's optional.
What happens if the JSON doesn't match the model?
FastAPI returns a 422 Unprocessable Entity with details about what's wrong. Send {"title": 123} and you'll get an error saying description is missing and title should be a string. No manual validation needed.
class User(BaseModel):
name: str
email: str
age: int
@app.post("/users")
def create_user(user: User):
return {"created": user.name, "email": user.email}
Try posting {"name": "Alice", "email": "a@b.com", "age": "not a number"} — FastAPI rejects it because "not a number" isn't a valid integer.
Can I add validation beyond just types? Like "age must be positive"?
Yes. Pydantic has Field() for extra constraints:
from pydantic import BaseModel, Field
class Task(BaseModel):
title: str = Field(min_length=1, max_length=100)
description: str = Field(default="", max_length=500)
priority: int = Field(default=1, ge=1, le=5)
min_length, max_length for strings. ge (greater-or-equal), le (less-or-equal), gt, lt for numbers. These constraints are enforced before your function even runs.
How do I access the fields inside the function?
Like any Python object — with dot notation:
@app.post("/tasks")
def create_task(task: Task):
print(task.title) # "Buy milk"
print(task.priority) # 1
data = task.model_dump() # converts to dict
return {"created": True, **data}
task.model_dump() converts the Pydantic model back to a plain dictionary. Useful when you need to store it in a database or merge it with other data.
Practice your skills
Sign up to write and run code in this lesson.
Request Bodies with Pydantic
Use Pydantic BaseModel to define and validate POST request bodies.
So far we've only done GET requests. How do I send data to the API — like creating a new user?
You use a POST request with a request body. In FastAPI, you define the shape of that body using a Pydantic model — a class that inherits from BaseModel:
from pydantic import BaseModel
from fastapi import FastAPI
app = FastAPI()
class Task(BaseModel):
title: str
description: str
priority: int = 1
@app.post("/tasks")
def create_task(task: Task):
return {"id": 1, "task": task.model_dump()}
When someone sends a POST to /tasks with JSON like {"title": "Buy milk", "description": "2%"}, FastAPI automatically parses it into a Task object. The priority field has a default of 1, so it's optional.
What happens if the JSON doesn't match the model?
FastAPI returns a 422 Unprocessable Entity with details about what's wrong. Send {"title": 123} and you'll get an error saying description is missing and title should be a string. No manual validation needed.
class User(BaseModel):
name: str
email: str
age: int
@app.post("/users")
def create_user(user: User):
return {"created": user.name, "email": user.email}
Try posting {"name": "Alice", "email": "a@b.com", "age": "not a number"} — FastAPI rejects it because "not a number" isn't a valid integer.
Can I add validation beyond just types? Like "age must be positive"?
Yes. Pydantic has Field() for extra constraints:
from pydantic import BaseModel, Field
class Task(BaseModel):
title: str = Field(min_length=1, max_length=100)
description: str = Field(default="", max_length=500)
priority: int = Field(default=1, ge=1, le=5)
min_length, max_length for strings. ge (greater-or-equal), le (less-or-equal), gt, lt for numbers. These constraints are enforced before your function even runs.
How do I access the fields inside the function?
Like any Python object — with dot notation:
@app.post("/tasks")
def create_task(task: Task):
print(task.title) # "Buy milk"
print(task.priority) # 1
data = task.model_dump() # converts to dict
return {"created": True, **data}
task.model_dump() converts the Pydantic model back to a plain dictionary. Useful when you need to store it in a database or merge it with other data.
Practice your skills
Sign up to write and run code in this lesson.