Request Data & Parameters
FastAPI allows you to handle incoming request data through various parameter types including path, query, header, cookie, and body parameters. You can also handle multi-part form data and file uploads using specialized classes.
In this tutorial, you will build an API for an item management system that demonstrates how to extract and validate data from every part of an HTTP request.
Prerequisites
To follow this tutorial, you need:
- FastAPI and Uvicorn installed.
- Pydantic (installed with FastAPI) for data validation.
- python-multipart installed (required for handling forms and files).
pip install fastapi uvicorn[standard] python-multipart
Step 1: Path and Query Parameters
Path parameters are extracted from the URL path, while query parameters are extracted from the URL query string (after the ?). You use Annotated with Path and Query to add metadata and validation.
Create a file main.py:
from typing import Annotated
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/items/{item_id}")
async def read_items(
item_id: Annotated[int, Path(title="The ID of the item to get", ge=1)],
q: Annotated[str | None, Query(alias="item-query", max_length=50)] = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
return results
In this example:
item_idis a Path parameter. It is required because it is part of the path. We added a numeric validationge=1(greater than or equal to 1) usingfastapi.Path.qis a Query parameter. It is optional because it has a default value ofNone. We usedfastapi.Queryto define an aliasitem-query(allowing the URL to be/items/5?item-query=fixed) and amax_lengthvalidation.
Step 2: Request Body and Multiple Parameters
To receive a JSON body, you use Pydantic models. If you have multiple models or want to embed a single model inside a JSON object, use fastapi.Body.
Update main.py:
from pydantic import BaseModel
from fastapi import Body
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Annotated[Item, Body(embed=True)],
user: User,
importance: Annotated[int, Body()] = 0,
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
FastAPI recognizes how to handle each parameter:
item_idis a path parameter (it's in the path).itemusesBody(embed=True), so FastAPI expects a JSON body like{"item": {...}}.useris a Pydantic model not in the path, so it's treated as the request body.importanceis a singular value (int) but we usedBody(), so it's also expected in the JSON body instead of as a query parameter.
Step 3: Headers and Cookies
Headers and cookies are extracted using fastapi.Header and fastapi.Cookie.
from fastapi import Cookie, Header
@app.get("/items/")
async def read_items_extra(
ads_id: Annotated[str | None, Cookie()] = None,
user_agent: Annotated[str | None, Header()] = None,
x_token: Annotated[list[str] | None, Header()] = None,
):
return {
"ads_id": ads_id,
"user_agent": user_agent,
"x_token": x_token,
}
Key features:
- Underscore Conversion: By default,
Headerconverts underscores to hyphens. A parameter nameduser_agentwill look for the HTTP headerUser-Agent. You can disable this withHeader(convert_underscores=False). - Duplicate Headers: If a header appears multiple times (like
X-Token), defining the type aslist[str]allows FastAPI to collect all values into a list.
Step 4: Forms and File Uploads
To receive form fields instead of JSON, use fastapi.Form. For file uploads, use fastapi.File and fastapi.UploadFile.
from fastapi import File, Form, UploadFile
@app.post("/files/")
async def create_file(
file: Annotated[bytes, File()],
fileb: Annotated[UploadFile, File()],
token: Annotated[str, Form()],
):
return {
"file_size": len(file),
"token": token,
"fileb_content_type": fileb.content_type,
"filename": fileb.filename,
}
Differences in file handling:
bytes: If you declare the type asbytes, FastAPI reads the entire file into memory. This is suitable for small files.UploadFile: This uses a "spooled" file (stored in memory up to a limit, then on disk). It provides an async interface:await fileb.read(size): Reads bytes.await fileb.write(data): Writes bytes.await fileb.seek(offset): Moves the file cursor.await fileb.close(): Closes the file.
Form: Used for standard HTML form fields (application/x-www-form-urlencoded).
Complete Working Result
Here is the complete main.py combining these concepts:
from typing import Annotated
from fastapi import FastAPI, Path, Query, Body, Cookie, Header, File, Form, UploadFile
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
@app.post("/items/{item_id}")
async def create_complex_item(
item_id: Annotated[int, Path(ge=1)],
item: Annotated[Item, Body(embed=True)],
q: Annotated[str | None, Query()] = None,
ads_id: Annotated[str | None, Cookie()] = None,
user_agent: Annotated[str | None, Header()] = None,
file: Annotated[UploadFile, File()] = None,
token: Annotated[str, Form()] = None,
):
return {
"item_id": item_id,
"item": item,
"q": q,
"ads_id": ads_id,
"user_agent": user_agent,
"filename": file.filename if file else None,
"token": token,
}
You can run this application using:
uvicorn main:app --reload
Visit /docs to see the automatically generated Swagger UI, which correctly identifies where each parameter should be sent (Path, Query, Body, Header, Cookie, or Multipart Form).