Python 8 min read

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.

MR

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

ValidatorDescriptionExample
gtGreater thangt=0
geGreater than or equalge=1
ltLess thanlt=100
leLess than or equalle=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

ValidatorDescriptionExample
min_lengthMinimum string lengthmin_length=1
max_lengthMaximum string lengthmax_length=255
patternRegex patternpattern="^[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

FeaturePathQuery
LocationURL pathAfter ?
Required by defaultYesNo
Syntax{param}?param=value
Use caseResource IDFiltering/Pagination
Multiple valuesNoYes (list)
Validator classPath()Query()
ValidationWorks OnExample
ge, gt, le, ltNumbersge=1, le=100
min_length, max_lengthStringsmin_length=3
patternStringspattern="^[a-z]+$"
enumBothcategory: 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

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

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

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.