FastAPI Tutorial Part 2: Path and Query Parameters - Complete Guide
Master FastAPI path and query parameters. Learn parameter validation, type conversion, optional parameters, and advanced patterns for building flexible APIs.
Moshiour Rahman
Advertisement
Understanding URL Parameters
FastAPI provides two main ways to receive data from URLs: path parameters (part of the URL path) and query parameters (after the ? in URLs).
https://api.example.com/users/123?include_posts=true&limit=10
^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Query Parameters
Path Parameter
Path Parameters
Path parameters are variable parts of a URL path, typically used to identify specific resources.
Basic Path Parameters
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
"""Fetch user by ID."""
return {"user_id": user_id}
@app.get("/posts/{post_id}/comments/{comment_id}")
def get_comment(post_id: int, comment_id: int):
"""Fetch specific comment from a post."""
return {
"post_id": post_id,
"comment_id": comment_id
}
Type Conversion
FastAPI automatically converts path parameters based on type hints:
@app.get("/items/{item_id}")
def get_item(item_id: int):
# item_id is automatically converted to int
return {"item_id": item_id, "type": type(item_id).__name__}
@app.get("/products/{price}")
def get_by_price(price: float):
# price is automatically converted to float
return {"price": price}
@app.get("/flags/{flag}")
def get_flag(flag: bool):
# Accepts: true, false, 1, 0, yes, no, on, off
return {"flag": flag}
Path Parameter Validation
Use Path for advanced validation:
from fastapi import FastAPI, Path
app = FastAPI()
@app.get("/items/{item_id}")
def get_item(
item_id: int = Path(
title="Item ID",
description="The unique identifier of the item",
ge=1, # greater than or equal to 1
le=1000 # less than or equal to 1000
)
):
return {"item_id": item_id}
@app.get("/users/{username}")
def get_user(
username: str = Path(
min_length=3,
max_length=50,
pattern="^[a-zA-Z0-9_]+$" # alphanumeric and underscores only
)
):
return {"username": username}
Numeric Validation Options
| Validator | Description | Example |
|---|---|---|
gt | Greater than | gt=0 |
ge | Greater than or equal | ge=1 |
lt | Less than | lt=100 |
le | Less than or equal | le=99 |
@app.get("/scores/{score}")
def validate_score(
score: int = Path(ge=0, le=100, description="Score between 0-100")
):
return {"score": score, "grade": "A" if score >= 90 else "B"}
String Validation Options
| Validator | Description | Example |
|---|---|---|
min_length | Minimum string length | min_length=1 |
max_length | Maximum string length | max_length=255 |
pattern | Regex pattern | pattern="^[a-z]+$" |
@app.get("/categories/{slug}")
def get_category(
slug: str = Path(
min_length=2,
max_length=100,
pattern="^[a-z0-9-]+$",
examples=["electronics", "home-garden"]
)
):
return {"category": slug}
Query Parameters
Query parameters are key-value pairs after ? in URLs, used for filtering, sorting, and pagination.
Basic Query Parameters
@app.get("/items")
def list_items(skip: int = 0, limit: int = 10):
"""
List items with pagination.
URL: /items?skip=0&limit=10
"""
return {"skip": skip, "limit": limit}
@app.get("/search")
def search(q: str, category: str = "all"):
"""
Search with query and optional category.
URL: /search?q=laptop&category=electronics
"""
return {"query": q, "category": category}
Required vs Optional Parameters
from typing import Optional
# Required parameter (no default value)
@app.get("/search")
def search_required(q: str):
return {"query": q}
# Optional parameter (has default value)
@app.get("/items")
def list_items(category: str = "all"):
return {"category": category}
# Optional parameter (can be None)
@app.get("/users")
def list_users(role: Optional[str] = None):
if role:
return {"filter": f"role={role}"}
return {"filter": "none"}
Query Parameter Validation
Use Query for advanced validation:
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items")
def list_items(
skip: int = Query(default=0, ge=0, description="Records to skip"),
limit: int = Query(default=10, ge=1, le=100, description="Max records"),
sort: str = Query(default="created_at", pattern="^[a-z_]+$")
):
return {"skip": skip, "limit": limit, "sort": sort}
@app.get("/search")
def search(
q: str = Query(
min_length=1,
max_length=100,
title="Search Query",
description="The search term to look for",
examples=["python", "fastapi tutorial"]
)
):
return {"query": q}
Multiple Values for Same Parameter
from typing import List
@app.get("/items")
def filter_items(
tags: List[str] = Query(default=[])
):
"""
Filter by multiple tags.
URL: /items?tags=python&tags=fastapi&tags=api
"""
return {"tags": tags}
@app.get("/products")
def filter_products(
category: List[str] = Query(
default=["all"],
description="Product categories to include"
)
):
return {"categories": category}
Deprecated Parameters
@app.get("/items")
def list_items(
q: str = Query(default=None),
# Mark old parameter as deprecated
search: str = Query(
default=None,
deprecated=True,
description="Use 'q' instead"
)
):
query = q or search
return {"query": query}
Combining Path and Query Parameters
@app.get("/users/{user_id}/posts")
def get_user_posts(
user_id: int = Path(ge=1, description="User ID"),
skip: int = Query(default=0, ge=0),
limit: int = Query(default=10, ge=1, le=50),
published: Optional[bool] = Query(default=None)
):
"""
Get posts for a specific user with pagination.
URL: /users/123/posts?skip=0&limit=10&published=true
"""
return {
"user_id": user_id,
"skip": skip,
"limit": limit,
"published_only": published
}
Enum Parameters
Use Python enums for predefined choices:
from enum import Enum
class SortOrder(str, Enum):
asc = "asc"
desc = "desc"
class Category(str, Enum):
electronics = "electronics"
clothing = "clothing"
books = "books"
home = "home"
@app.get("/items")
def list_items(
category: Category,
order: SortOrder = SortOrder.desc
):
return {"category": category.value, "order": order.value}
@app.get("/products/{category}")
def get_products(category: Category):
"""Category is validated against enum values."""
return {"category": category}
Path Order Matters
Be careful with path order - specific paths should come before generic ones:
# CORRECT ORDER
@app.get("/users/me") # Specific path first
def get_current_user():
return {"user": "current"}
@app.get("/users/{user_id}") # Generic path second
def get_user(user_id: int):
return {"user_id": user_id}
# WRONG ORDER would make /users/me unreachable
# @app.get("/users/{user_id}") # This would catch "me" as user_id
# @app.get("/users/me") # Never reached
Advanced Patterns
Alias Parameters
@app.get("/items")
def list_items(
# Python name vs API name
item_query: str = Query(alias="item-query")
):
"""
Use alias for non-Python-friendly parameter names.
URL: /items?item-query=laptop
"""
return {"query": item_query}
Hidden Parameters
@app.get("/admin/items")
def admin_items(
# Hidden from OpenAPI documentation
internal_key: str = Query(include_in_schema=False)
):
return {"key_provided": bool(internal_key)}
Complex Validation with Annotated
from typing import Annotated
# Define reusable parameter types
UserId = Annotated[int, Path(ge=1, description="User ID")]
Pagination = Annotated[int, Query(ge=0, le=100)]
@app.get("/users/{user_id}")
def get_user(user_id: UserId):
return {"user_id": user_id}
@app.get("/users/{user_id}/posts")
def get_posts(user_id: UserId, limit: Pagination = 10):
return {"user_id": user_id, "limit": limit}
Practical Example: E-commerce API
from fastapi import FastAPI, Path, Query
from typing import Optional, List
from enum import Enum
app = FastAPI()
class SortField(str, Enum):
price = "price"
name = "name"
rating = "rating"
date = "date"
class SortOrder(str, Enum):
asc = "asc"
desc = "desc"
# Product listing with filtering
@app.get("/products")
def list_products(
# Filtering
category: Optional[str] = Query(default=None, min_length=2),
min_price: Optional[float] = Query(default=None, ge=0),
max_price: Optional[float] = Query(default=None, ge=0),
in_stock: Optional[bool] = Query(default=None),
tags: List[str] = Query(default=[]),
# Sorting
sort_by: SortField = Query(default=SortField.date),
order: SortOrder = Query(default=SortOrder.desc),
# Pagination
page: int = Query(default=1, ge=1),
per_page: int = Query(default=20, ge=1, le=100)
):
"""
Advanced product listing with filtering, sorting, and pagination.
Example URLs:
- /products?category=electronics&min_price=100&max_price=500
- /products?tags=wireless&tags=bluetooth&in_stock=true
- /products?sort_by=price&order=asc&page=2&per_page=50
"""
return {
"filters": {
"category": category,
"price_range": {"min": min_price, "max": max_price},
"in_stock": in_stock,
"tags": tags
},
"sorting": {
"field": sort_by.value,
"order": order.value
},
"pagination": {
"page": page,
"per_page": per_page,
"offset": (page - 1) * per_page
}
}
# Single product with related items
@app.get("/products/{product_id}")
def get_product(
product_id: int = Path(ge=1, description="Product ID"),
include_reviews: bool = Query(default=False),
include_related: bool = Query(default=False),
related_limit: int = Query(default=5, ge=1, le=20)
):
"""
Get product details with optional includes.
URL: /products/123?include_reviews=true&include_related=true
"""
return {
"product_id": product_id,
"includes": {
"reviews": include_reviews,
"related": include_related,
"related_limit": related_limit if include_related else None
}
}
# Category browsing
@app.get("/categories/{category_slug}/products")
def category_products(
category_slug: str = Path(min_length=2, pattern="^[a-z0-9-]+$"),
subcategory: Optional[str] = Query(default=None),
featured_only: bool = Query(default=False)
):
"""
Browse products by category.
URL: /categories/electronics/products?subcategory=phones&featured_only=true
"""
return {
"category": category_slug,
"subcategory": subcategory,
"featured_only": featured_only
}
# Search endpoint
@app.get("/search")
def search_products(
q: str = Query(min_length=2, max_length=100, description="Search query"),
category: Optional[str] = Query(default=None),
sort_by: SortField = Query(default=SortField.rating),
page: int = Query(default=1, ge=1)
):
"""
Search products with optional category filter.
URL: /search?q=wireless headphones&category=electronics
"""
return {
"query": q,
"category": category,
"sort_by": sort_by.value,
"page": page
}
Error Handling for Invalid Parameters
FastAPI automatically returns validation errors:
# Request: GET /items/-5
# Response (422 Unprocessable Entity):
{
"detail": [
{
"type": "greater_than_equal",
"loc": ["path", "item_id"],
"msg": "Input should be greater than or equal to 1",
"input": "-5",
"ctx": {"ge": 1}
}
]
}
Custom validation messages:
from fastapi import HTTPException
@app.get("/users/{user_id}")
def get_user(user_id: int = Path(ge=1)):
# Additional business logic validation
if user_id > 10000:
raise HTTPException(
status_code=404,
detail=f"User {user_id} not found"
)
return {"user_id": user_id}
Summary
| Feature | Path | Query |
|---|---|---|
| Location | URL path | After ? |
| Required by default | Yes | No |
| Syntax | {param} | ?param=value |
| Use case | Resource ID | Filtering/Pagination |
| Multiple values | No | Yes (list) |
| Validator class | Path() | Query() |
| Validation | Works On | Example |
|---|---|---|
ge, gt, le, lt | Numbers | ge=1, le=100 |
min_length, max_length | Strings | min_length=3 |
pattern | Strings | pattern="^[a-z]+$" |
enum | Both | category: Category |
Next Steps
In Part 3, we’ll explore Request Bodies and Pydantic - learning how to handle complex JSON data, nested models, and advanced validation.
Series Navigation:
- Part 1: Introduction and Setup
- Part 2: Path and Query Parameters (You are here)
- Part 3: Request Bodies and Pydantic
- Part 4: Response Models and Status Codes
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
FastAPI Tutorial Part 1: Introduction and Setup - Build Modern Python APIs
Start your FastAPI journey with this comprehensive guide. Learn installation, create your first API, understand async Python, and explore automatic documentation.
PythonFastAPI Tutorial Part 4: Response Models and Status Codes - Control Your API Output
Learn to control FastAPI responses with response models, status codes, and headers. Master data filtering, multiple response types, and proper HTTP semantics.
PythonFastAPI Tutorial: Build Modern Python APIs
Master FastAPI for building high-performance Python APIs. Learn async endpoints, validation, authentication, database integration, and deployment.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.