Skip to course content
Free FastAPI backend course

FastAPI for AI Backend Development

Unit 04.02: Request bodies and where validation happens

Body validation happens before your handler runs, which is the whole point.

Rejected before the first line executes

A create endpoint with a pattern and a numeric range on the body model.

The code posts four payloads, three of them invalid.

from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel, Field

app = FastAPI()


class CreateInvoice(BaseModel):
    account: str = Field(pattern=r"^ACC-\d+$")
    amount: float = Field(gt=0, le=100000)


@app.post("/invoices", status_code=201)
def create(body: CreateInvoice) -> dict:
    return {"created": body.account, "amount": body.amount}


client = TestClient(app)
for payload in [{"account": "ACC-1", "amount": 240.0},
                {"account": "1", "amount": 240.0},
                {"account": "ACC-1", "amount": -5},
                {"account": "ACC-1"}]:
    r = client.post("/invoices", json=payload)
    print(f"{str(payload):44} -> {r.status_code}")

print("\nAll three rejections happened before the handler body ran.")

Three of the four never reach the handler. The malformed account, the negative amount and the missing field are each rejected by the framework with a 422 naming the field.

That is validation you did not write and cannot forget to call - and every constraint is simultaneously in the documentation, which is where the caller finds out before sending anything.

The mistake this prevents

The mistake is validating inside the handler with if statements. They work, they are invisible to the schema, and the next endpoint gets a slightly different version of the same checks.

Takeaway

Put constraints on the model. They run before your code, they cannot be forgotten, and they appear in the published contract automatically.