Skip to main content

FastAPI Parameters

When building APIs, one of the most common requirements is accepting and validating input data. FastAPI provides multiple ways to receive input parameters in your endpoints, each suited for different use cases. In this tutorial, we'll explore the various parameter types in FastAPI and how to effectively use them.

Introduction to FastAPI Parameters

Parameters in FastAPI are how your API receives data. They can come from different places:

  • Path parameters: Part of the URL path
  • Query parameters: After the ? in the URL
  • Request body: Data sent in the request body (typically in JSON format)
  • Form data: Data submitted through HTML forms
  • Header parameters: Custom headers in the HTTP request
  • Cookie parameters: Data from cookies

FastAPI's parameter handling is powerful because it:

  • Automatically validates data
  • Converts data to Python types
  • Generates OpenAPI documentation
  • Provides clear error messages when validation fails

Let's dive into each type of parameter.

Path Parameters

Path parameters are parts of the URL path that can change. They're defined in the route decorator using curly braces {}.

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} in the route defines a path parameter
  • item_id: int in the function parameters tells FastAPI to convert the path parameter to an integer
  • If the conversion fails, FastAPI returns a clear error response

When you call /items/5, you'll get:

json
{"item_id": 5}

If you call /items/abc, you'll get an error because "abc" can't be converted to an integer.

Path Parameters with Predefined Values

You can restrict path parameters to a set of valid 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 == 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"}

This ensures that only "alexnet", "resnet", or "lenet" are accepted as valid values.

Query Parameters

Query parameters are the key-value pairs that appear after the ? in a URL. In FastAPI, function parameters that aren't part of the path become query parameters.

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}

This endpoint accepts requests like /items/?skip=20&limit=50.

If you call /items/?skip=20&limit=50, you'll get:

json
{"skip": 20, "limit": 50}

If you call /items/, you'll get the default values:

json
{"skip": 0, "limit": 10}

Optional Query Parameters

To make a query parameter optional, you can use Python's Optional type hint:

python
from typing import Optional
from fastapi import FastAPI

app = FastAPI()

@app.get("/items/")
async def read_items(name: Optional[str] = None):
if name is None:
return {"message": "No name provided"}
return {"name": name}

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: int):
return {"item_id": item_id}

Calling this endpoint without providing item_id will result in an error.

Request Body Parameters

When you need to send data that's too complex or large for query parameters, you use the request body. In FastAPI, you define the structure of the body using Pydantic models.

Basic Request Body

python
from fastapi import FastAPI
from pydantic import BaseModel

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

app = FastAPI()

@app.post("/items/")
async def create_item(item: Item):
item_dict = item.dict()
if item.tax:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict

This endpoint expects a JSON body like:

json
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}

And would respond with:

json
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5,
"price_with_tax": 48.7
}

Combining Path, Query, and Body Parameters

FastAPI allows you to combine different types of parameters:

python
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel

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

app = FastAPI()

@app.put("/items/{item_id}")
async def update_item(
item_id: int,
item: Item,
q: Optional[str] = None
):
result = {"item_id": item_id, **item.dict()}
if q:
result.update({"q": q})
return result

This endpoint:

  • Takes a path parameter item_id
  • Takes a request body defined by the Item model
  • Takes an optional query parameter q

Form Data Parameters

When working with HTML forms or file uploads, you need to use form data. FastAPI provides the Form class for this:

python
from fastapi import FastAPI, Form

app = FastAPI()

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

Note: You need to install python-multipart to use form data:

pip install python-multipart

File Upload Parameters

To handle file uploads, FastAPI provides the File and UploadFile classes:

python
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/files/")
async def create_file(file: bytes = File(...)):
return {"file_size": len(file)}

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

UploadFile is generally recommended over bytes for larger files as it doesn't load the entire file into memory.

You can also get data from headers and cookies:

python
from fastapi import FastAPI, Header, Cookie

app = FastAPI()

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

Parameter Validation

FastAPI provides powerful parameter validation through Pydantic:

python
from typing import List, Optional
from fastapi import FastAPI, Query
from pydantic import BaseModel

app = FastAPI()

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

Similar validation can be applied to Path, Body, and other parameter types.

Dependency Injection with Parameters

FastAPI's dependencies system works hand-in-hand with parameters, allowing you to create reusable components:

python
from fastapi import FastAPI, Depends, Header, HTTPException

app = FastAPI()

async def verify_token(x_token: str = Header(...)):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
return x_token

@app.get("/items/")
async def read_items(token: str = Depends(verify_token)):
return {"token": token, "items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}

Real-World Example: Building a Product API

Let's build a more complete example that demonstrates various parameter types in a product API:

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

app = FastAPI()

class Product(BaseModel):
id: Optional[int] = None
name: str = Field(..., min_length=1, max_length=100)
description: Optional[str] = Field(None, max_length=1000)
price: float = Field(..., gt=0)
category: str
in_stock: bool = True

# In-memory database
products_db = {
1: Product(id=1, name="Laptop", price=999.99, category="Electronics",
description="A high-performance laptop"),
2: Product(id=2, name="Smartphone", price=499.99, category="Electronics",
description="Latest smartphone model"),
3: Product(id=3, name="Coffee Mug", price=11.99, category="Kitchen",
description="Ceramic coffee mug")
}

@app.get("/products/", response_model=List[Product])
async def get_products(
skip: int = Query(0, ge=0, description="Number of products to skip"),
limit: int = Query(10, ge=1, le=100, description="Max number of products to return"),
category: Optional[str] = Query(None, description="Filter by category")
):
"""Get a list of products with pagination and optional category filtering."""
products = list(products_db.values())

if category:
products = [p for p in products if p.category == category]

return products[skip:skip+limit]

@app.get("/products/{product_id}", response_model=Product)
async def get_product(
product_id: int = Path(..., ge=1, description="The ID of the product to retrieve")
):
"""Get a single product by its ID."""
if product_id not in products_db:
raise HTTPException(status_code=404, detail="Product not found")
return products_db[product_id]

@app.post("/products/", response_model=Product)
async def create_product(
product: Product = Body(..., description="The product data"),
):
"""Create a new product."""
# Generate a new ID
new_id = max(products_db.keys()) + 1
product.id = new_id
products_db[new_id] = product
return product

@app.put("/products/{product_id}", response_model=Product)
async def update_product(
product_id: int = Path(..., ge=1),
product_update: Product = Body(...),
partial: bool = Query(False, description="Whether to perform a partial update")
):
"""Update an existing product."""
if product_id not in products_db:
raise HTTPException(status_code=404, detail="Product not found")

existing_product = products_db[product_id]
update_data = product_update.dict(exclude_unset=partial)
update_data["id"] = product_id # Ensure we don't change the ID

updated_product = Product(**update_data)
products_db[product_id] = updated_product
return updated_product

@app.delete("/products/{product_id}")
async def delete_product(
product_id: int = Path(..., ge=1)
):
"""Delete a product."""
if product_id not in products_db:
raise HTTPException(status_code=404, detail="Product not found")

deleted_product = products_db.pop(product_id)
return {"message": f"Product '{deleted_product.name}' deleted"}

This example shows how to:

  1. Define a Pydantic model with validations
  2. Use path parameters with validation
  3. Use query parameters for pagination and filtering
  4. Work with request bodies for creating and updating data
  5. Use proper status codes and error handling
  6. Document your API with docstrings and parameter descriptions

Summary

FastAPI provides a comprehensive system for handling different types of parameters in your API endpoints. We've covered:

  • Path parameters for variable parts of URL paths
  • Query parameters for optional data and filtering
  • Request body parameters for complex data structures
  • Form data and file uploads for HTML forms and files
  • Header and cookie parameters for HTTP headers and cookies
  • Parameter validation using Pydantic
  • Dependency injection with parameters

By understanding these concepts, you can build robust and well-documented APIs that are easy to use and maintain.

Additional Resources

Exercises

  1. Create an API endpoint that accepts a user ID in the path and returns user data
  2. Create an endpoint that accepts query parameters for filtering a list of items
  3. Add data validation to ensure a price parameter is always positive
  4. Create a form that uploads a file and returns information about the uploaded file
  5. Implement an endpoint that creates a new resource and returns appropriate HTTP status codes


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