Skip to main content

FastAPI Request Extractors

When building APIs with FastAPI, you'll frequently need to extract data from incoming requests. FastAPI provides several elegant ways to retrieve data from different parts of an HTTP request. These "request extractors" help you access information like query parameters, path parameters, request bodies, headers, and cookies.

Introduction to Request Extractors

Request extractors are special function parameters that tell FastAPI where to look for specific values in incoming requests. By using type hints and parameter declarations, FastAPI automatically:

  1. Extracts the values from the correct request location
  2. Validates the data based on the type annotations
  3. Converts the data to the appropriate Python type
  4. Generates OpenAPI documentation

Let's explore the different types of request extractors available in FastAPI.

Path Parameters

Path parameters are parts of the URL path that are variable. They are essential for RESTful API design when identifying specific resources.

Basic Path Parameters

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}

In this example, item_id is a path parameter that will be extracted from the URL. If you access /items/42, FastAPI will:

  • Extract 42 from the URL
  • Convert it to an integer (since we declared item_id: int)
  • Pass it to the function

Multiple Path Parameters

You can have multiple path parameters in a single endpoint:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}/items/{item_id}")
async def get_user_item(user_id: int, item_id: str):
return {"user_id": user_id, "item_id": item_id}

If you access /users/123/items/laptop, FastAPI will extract 123 as user_id and laptop as item_id.

Path Parameter with Predefined Values

You can restrict path parameters to a set of values using Pydantic's Enum:

python
from enum import Enum
from fastapi import FastAPI

class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"

app = FastAPI()

@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}

if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}

return {"model_name": model_name, "message": "Have some residuals"}

Query Parameters

Query parameters are the key-value pairs that appear after the ? in a URL. They're typically used for filtering, sorting, or pagination.

Basic Query Parameters

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}

If you access /items/?skip=20&limit=50, FastAPI will extract skip=20 and limit=50.

Optional Query Parameters

You can make query parameters optional by setting a default value or using Optional:

python
from typing import Optional
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(name: Optional[str] = None, price: float = None):
response = {}
if name:
response["name"] = name
if price:
response["price"] = price
return response

Required Query Parameters

If you don't provide a default value, the parameter becomes required:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(item_id: str):
return {"item_id": item_id}

Now, requests to /items/ without the item_id query parameter will return an error.

Request Body

The request body is data sent by the client in the HTTP request. FastAPI uses Pydantic models to define, validate, and document request bodies.

Basic Request Body

python
from fastapi import FastAPI
from pydantic import BaseModel

class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None

app = FastAPI()

@app.post("/items/")
async def create_item(item: Item):
return item

Example request:

json
{
"name": "Foo",
"price": 45.2,
"description": "Optional description"
}

FastAPI will:

  1. Read the request body as JSON
  2. Convert the corresponding types
  3. Validate the data
  4. Provide the data in the item parameter

Multiple Body Parameters

You can have multiple body parameters:

python
from fastapi import FastAPI
from pydantic import BaseModel

class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None

class User(BaseModel):
username: str
full_name: str = None

app = FastAPI()

@app.post("/items/")
async def create_item(item: Item, user: User):
return {"item": item, "user": user}

FastAPI will expect a JSON body with both the item and user data.

Form Data

For handling HTML form data, you can use Form fields:

python
from fastapi import FastAPI, Form

app = FastAPI()

@app.post("/login/")
async def login(username: str = Form(...), password: str = Form(...)):
return {"username": username}

This requires installing python-multipart:

pip install python-multipart

Headers

To extract data from HTTP headers:

python
from fastapi import FastAPI, Header
from typing import Optional

app = FastAPI()

@app.get("/items/")
async def read_items(user_agent: Optional[str] = Header(None)):
return {"User-Agent": user_agent}

FastAPI automatically converts header names from snake_case to kebab-case, so user_agent will look for the User-Agent header.

Cookies

To extract cookies from the request:

python
from fastapi import FastAPI, Cookie
from typing import Optional

app = FastAPI()

@app.get("/items/")
async def read_items(session_token: Optional[str] = Cookie(None)):
return {"session_token": session_token}

File Uploads

For handling file uploads:

python
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
return {"filename": file.filename}

Combining Different Parameter Types

A real-world API endpoint often combines multiple parameter types:

python
from typing import Optional
from fastapi import FastAPI, Header, Path, Query
from pydantic import BaseModel

class Item(BaseModel):
name: str
description: Optional[str] = None
price: float

app = FastAPI()

@app.put("/users/{user_id}/items/{item_id}")
async def update_item(
*,
user_id: int = Path(..., title="The ID of the user"),
item_id: int = Path(..., title="The ID of the item to get"),
item: Item,
q: Optional[str] = Query(None, max_length=50),
user_agent: Optional[str] = Header(None)
):
result = {"user_id": user_id, "item_id": item_id, "item": item}
if q:
result.update({"q": q})
if user_agent:
result.update({"user_agent": user_agent})
return result

In this example, we're combining:

  • Path parameters (user_id, item_id)
  • Request body (item)
  • Query parameter (q)
  • Header (user_agent)

Request Object

Sometimes you might need access to the raw request object:

python
from fastapi import FastAPI, Request

app = FastAPI()

@app.get("/items/{item_id}")
async def read_root(item_id: str, request: Request):
client_host = request.client.host
request_headers = request.headers
return {
"item_id": item_id,
"client_host": client_host,
"request_headers": dict(request_headers)
}

Advanced Request Validation

FastAPI integrates with Pydantic for advanced validation:

python
from fastapi import FastAPI, Query
from typing import List

app = FastAPI()

@app.get("/items/")
async def read_items(
q: str = Query(
None,
title="Query string",
description="Query string for the items to search in the database",
min_length=3,
max_length=50,
regex="^fixedquery$",
deprecated=False,
)
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results

Practical Example: Building a RESTful API

Let's build a simple TODO API that demonstrates how to use different request extractors together:

python
from fastapi import FastAPI, Path, Query, Body, HTTPException, Header
from typing import List, Optional
from pydantic import BaseModel, Field
from datetime import datetime

app = FastAPI()

# Models
class TodoItem(BaseModel):
title: str = Field(..., min_length=1, max_length=100)
description: Optional[str] = Field(None, max_length=500)
priority: int = Field(1, ge=1, le=5)
completed: bool = False

class TodoResponse(TodoItem):
id: int
created_at: datetime

# In-memory database
todos_db = {}
todo_counter = 0

@app.post("/todos/", response_model=TodoResponse, status_code=201)
async def create_todo(
item: TodoItem,
user_id: Optional[str] = Header(None, convert_underscores=False)
):
# Require user_id header for authentication
if not user_id:
raise HTTPException(status_code=401, detail="User ID header is required")

global todo_counter
todo_counter += 1
new_todo = TodoResponse(
**item.dict(),
id=todo_counter,
created_at=datetime.now()
)
todos_db[todo_counter] = new_todo
return new_todo

@app.get("/todos/", response_model=List[TodoResponse])
async def get_todos(
skip: int = Query(0, ge=0),
limit: int = Query(10, ge=1, le=100),
completed: Optional[bool] = Query(None)
):
# Filter and paginate todos
filtered_todos = list(todos_db.values())

if completed is not None:
filtered_todos = [todo for todo in filtered_todos if todo.completed == completed]

return filtered_todos[skip: skip + limit]

@app.get("/todos/{todo_id}", response_model=TodoResponse)
async def get_todo(todo_id: int = Path(..., gt=0)):
if todo_id not in todos_db:
raise HTTPException(status_code=404, detail="Todo not found")
return todos_db[todo_id]

@app.put("/todos/{todo_id}", response_model=TodoResponse)
async def update_todo(
update_data: TodoItem,
todo_id: int = Path(..., gt=0)
):
if todo_id not in todos_db:
raise HTTPException(status_code=404, detail="Todo not found")

todo = todos_db[todo_id]
updated_todo = todo.copy(update=update_data.dict(exclude_unset=True))
todos_db[todo_id] = updated_todo
return updated_todo

@app.delete("/todos/{todo_id}", status_code=204)
async def delete_todo(todo_id: int = Path(..., gt=0)):
if todo_id not in todos_db:
raise HTTPException(status_code=404, detail="Todo not found")

del todos_db[todo_id]
return None

Summary

FastAPI's request extractors provide a powerful and type-safe way to extract data from HTTP requests:

  1. Path Parameters: Extract values from the URL path (/items/{item_id})
  2. Query Parameters: Extract values from URL query string (/items/?skip=0&limit=10)
  3. Request Body: Extract JSON data sent in the request body
  4. Form Data: Extract data from form submissions
  5. Headers: Extract values from HTTP headers
  6. Cookies: Extract values from cookies
  7. Files: Handle file uploads

Using these extractors with appropriate type hints gives you:

  • Automatic data validation
  • Data conversion to Python types
  • Clear API documentation
  • Better code organization

By combining different extractors, you can create comprehensive API endpoints that handle data from various parts of the HTTP request in a clean, type-safe manner.

Additional Resources

Exercises

  1. Create an API endpoint that accepts a user ID as a path parameter, a sort direction as a query parameter, and user data in the request body.
  2. Build an endpoint that accepts file uploads along with metadata about the file in JSON format.
  3. Create an API endpoint that extracts authentication information from headers and returns different responses based on the user's role.
  4. Build a search API that accepts multiple search filters as query parameters with validation (min length, max length, etc.).
  5. Extend the TODO API example with the ability to assign tags to TODO items and search by tags.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)