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

· Updated May 9, 2026

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
Is your AI API down right now? Check live status for OpenAI, Claude, Gemini, Groq, and more.
Check status →
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.