Day 6 · ~12m

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.

Already have an account? Sign in