Routing and Endpoints
Learn how to create and manage routes and endpoints effectively in FastAPI applications.
Content
Request Bodies
Versions:
Watch & Learn
AI-discovered learning video
Sign in to watch the learning video for this topic.
Request Bodies in FastAPI — Feed the API, Not the Gremlins
"Query parameters tell the API where to look; path parameters show which thing; request bodies tell the API what the thing actually is." — Your backend, probably.
You already learned how to grab stuff from the URL: path parameters (the specific item) and query parameters (filters, pagination, the jazz hands). Now we move to the meaty part: request bodies — the actual payloads clients send (usually JSON, forms, files) when they want you to create or update resources. FastAPI makes this delightful with Pydantic-powered validation, automatic docs, and error messages that won’t make you cry into your keyboard.
Why request bodies matter (and why you should care)
- They carry the structured data your endpoints act on: user creation info, product details, lists of items, nested objects, etc.
- FastAPI auto-validates them using Pydantic models, so you get type-checking, default values, and errors that explain what went wrong.
- Proper design of request bodies improves API reliability, security, and developer experience.
Quick refresher connection: unlike path and query parameters (which are extracted from the URL), request bodies are in the request payload (HTTP body). They’re typically used for POST, PUT, PATCH requests.
The simplest example: JSON body with Pydantic
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.post('/items/')
async def create_item(item: Item):
return {'received': item}
- Annotating the parameter with a Pydantic model (Item) tells FastAPI: "This comes from the request body."
- Validation happens automatically. If
priceis missing or not a float, FastAPI returns a 422 error with details.
Why Pydantic? Because it transforms and validates: missing fields, wrong types, defaults, nested objects — all handled.
Useful patterns and niceties
Default values and Field metadata
from pydantic import Field
class Item(BaseModel):
name: str = Field(..., min_length=1)
price: float = Field(..., gt=0)
tags: list[str] = []
- Use Field(...) to declare validation rules and metadata (min_length, gt, lt, description).
...means required. Pydantic + FastAPI will make Swagger/OpenAPI show it as required.
Nested models and lists
class Address(BaseModel):
city: str
zipcode: str
class User(BaseModel):
name: str
addresses: list[Address]
Clients can POST nested JSON arrays/objects; FastAPI validates recursively.
Optional fields and defaults
Use typing.Optional (or X | None) and defaults. If omitted, values are filled with defaults or None.
When the body isn't JSON: forms, files, raw bytes
- For HTML forms: use Form()
- For file uploads: use File() and UploadFile
- For raw bytes or text: just accept bytes or str
Example: form + file upload
from fastapi import Form, File, UploadFile
@app.post('/upload/')
async def upload(name: str = Form(...), file: UploadFile = File(...)):
contents = await file.read()
return {'filename': file.filename, 'name': name, 'size': len(contents)}
Note: Request body parsing differs depending on Content-Type header: application/json, multipart/form-data, application/x-www-form-urlencoded, etc.
Mixing body with path and query parameters
FastAPI is explicit about parameter sources:
- Path params: forced by function signature and path template
- Query params: simple types without a model
- Body params: Pydantic models or parameters using Body()
Example:
@app.put('/users/{user_id}')
async def update_user(user_id: int, q: str | None = None, user: User = Body(...)):
return {'user_id': user_id, 'q': q, 'user': user}
Ordering in the function signature does not determine source — the types and annotations do.
Multiple body parameters / embedding
If you have multiple body params, FastAPI will expect them as separate JSON fields unless you embed them.
from fastapi import Body
@app.post('/mix/')
async def mix(a: int = Body(...), b: int = Body(...)):
return {'sum': a + b}
# OR embed a single model:
@app.post('/embed/')
async def embed_item(item: Item = Body(..., embed=True)):
return item
Use embed=True to wrap the model in another JSON key if your client sends { "item": { ... } }.
Error handling and validation responses
FastAPI returns 422 Unprocessable Entity with a JSON body describing which fields failed and why. This is gold for debugging. You can customize validation error handling with exception handlers if you want custom messages.
Quick reference table
| Parameter source | Use when... | FastAPI helper |
|---|---|---|
| Path | Identifying resource in URL | Typed function param + path template |
| Query | Filters, options, pagination | Typed param with default or Query() |
| Body | Complex structured data (JSON) | Pydantic model or Body() |
| Form | HTML forms (x-www-form-urlencoded) | Form() |
| File | File uploads | File(), UploadFile |
Pitfalls and best practices
- Don’t use mutable default values for Pydantic fields — use default_factory.
- Prefer Pydantic models for clarity and documentation.
- Use response_model to control what you return and avoid leaking internal fields (passwords, secrets).
- Validate size and types on uploads to avoid DoS with huge files.
- When receiving JSON lists, annotate as list[Item] — FastAPI will validate every element.
Final mic-drop summary (TL;DR)
- Request bodies are where clients send full objects; use Pydantic models for structure and validation.
- FastAPI auto-docs everything, validates inputs, and gives helpful errors.
- Mix path, query, and body parameters freely — FastAPI figures out where each param comes from by annotation.
Pro tip: If your endpoint is a verb that changes state (create/update), it's probably using a request body. Treat the body like the contract between client and server — make it explicit, validated, and well-documented.
Go forth and design APIs where the data is honest, validation is ruthless, and your swagger docs look like a resume. You’ve got this. 🧠✨
Comments (0)
Please sign in to leave a comment.
No comments yet. Be the first to comment!