Skip to main content

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_id is a Path parameter. It is required because it is part of the path. We added a numeric validation ge=1 (greater than or equal to 1) using fastapi.Path.
  • q is a Query parameter. It is optional because it has a default value of None. We used fastapi.Query to define an alias item-query (allowing the URL to be /items/5?item-query=fixed) and a max_length validation.

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_id is a path parameter (it's in the path).
  • item uses Body(embed=True), so FastAPI expects a JSON body like {"item": {...}}.
  • user is a Pydantic model not in the path, so it's treated as the request body.
  • importance is a singular value (int) but we used Body(), 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, Header converts underscores to hyphens. A parameter named user_agent will look for the HTTP header User-Agent. You can disable this with Header(convert_underscores=False).
  • Duplicate Headers: If a header appears multiple times (like X-Token), defining the type as list[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 as bytes, 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).