Path & Query Parameters

RoadmapsPython Backend

Scenario

Ever seen a route for `/users/me` return a 422 validation error saying 'me' is not a valid integer? That happens when dynamic path parameters swallow requests meant for static routes.

Overview

Path parameters identify a specific resource in a URL, while query parameters provide optional instructions like filtering or sorting.

They are the standard way HTTP clients send scalar data. Frameworks like FastAPI map these directly to Python function arguments and validate them automatically.

Where used: RESTful resource lookups (e.g., `/users/123`)., Pagination (e.g., `?limit=10&offset=20`)., Filtering lists (e.g., `?status=active`).

Why learn this

Mental model

Path parameters are the 'address' of a specific house (required to find it). Query parameters are the 'instructions' you leave in the mailbox (optional modifiers).

Deep dive

Path parameters are variables embedded in the URL path. In FastAPI, you define them in the route decorator using curly braces `{}` and match the name in the function signature. Because HTTP paths are strings, type hints (like `int`) tell FastAPI to automatically convert and validate the incoming data.

Any function parameter that is NOT explicitly part of the path template becomes a query parameter. They appear after the `?` in the URL as key-value pairs (`?skip=0&limit=10`). They are typically used for optional settings like pagination or filters.

Frameworks evaluate routes top-to-bottom. If you have a dynamic path like `/users/{user_id}` defined *before* a static path like `/users/me`, the framework will try to parse 'me' as a `user_id`. Always declare static, specific routes before dynamic ones.

Code examples

The Routing Bug

from fastapi import FastAPI

app = FastAPI()

# ❌ BUG: Dynamic route defined before static route
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}

@app.get("/users/me")
def get_current_user():
    return {"user_id": "current_user"}

If a client requests `/users/me`, it hits the first route. FastAPI tries to convert 'me' to an `int` and throws a 422 Validation Error.

The Fix

from fastapi import FastAPI

app = FastAPI()

# ✅ FIX: Static routes must precede dynamic ones
@app.get("/users/me")
def get_current_user():
    return {"user_id": "current_user"}

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}

Now, `/users/me` matches first. Any other `/users/123` falls through to the dynamic path parameter route.

Combining Path and Query Parameters

from fastapi import FastAPI
from typing import Optional

app = FastAPI()

@app.get("/items/{item_id}")
def read_item(
    item_id: int,           # Path parameter (required)
    q: Optional[str] = None, # Query parameter (optional)
    limit: int = 10         # Query parameter (optional, defaults to 10)
):
    return {"item_id": item_id, "q": q, "limit": limit}

Because `q` and `limit` aren't in the path `/items/{item_id}`, FastAPI knows they are query parameters. A request to `/items/42?q=test` automatically populates the variables.

Common mistakes

Recall questions

Understanding checks

If a client sends a GET request to `/files/report?format=pdf`, what will this endpoint return?

`{"file": "report", "fmt": "pdf"}`

`file_name` is extracted from the path ('report'), and `format` is extracted from the query string, overriding the default 'csv' with 'pdf'.

This API has two endpoints for fetching posts. Why is the 'latest' endpoint failing with validation errors?

The dynamic path `/posts/{post_id}` is defined *before* the static path `/posts/latest`.

FastAPI evaluates routes in the order they are defined. The router sees `/posts/latest`, matches it to `/posts/{post_id}`, and fails trying to convert the string 'latest' into an integer `post_id`.

Questions & answers

How do you define optional query parameters in FastAPI, and how does validation work?

You define them as function arguments with a default value, usually `None`. FastAPI validates their types based on the Python type hints and automatically returns a 422 error if the types are incorrect.

Approach: Explain that parameters not in the path template are query parameters by default. Emphasize that providing a default value makes them optional, and that type hints power the auto-validation and OpenAPI schema generation.

What happens if you have a dynamic route like `/users/{id}` defined before a static route like `/users/me`?

FastAPI evaluates routes sequentially. The dynamic route `/users/{id}` will match the request for `/users/me`. If `id` is typed as an integer, it will fail with a 422 error because 'me' cannot be parsed as an integer. Static routes must always be defined before dynamic routes.

Approach: Identify the routing order issue immediately. Explain the top-down matching mechanism of the router and state the best practice of placing specific (static) routes above generic (dynamic) ones.

Practice tasks

Add Pagination

Modify the endpoint to support pagination using query parameters `skip` and `limit`. Make `skip` default to 0 and `limit` default to 10.

Challenge

Combined Parameters

Create an endpoint at `/departments/{dept_id}/employees` that takes the `dept_id` as an integer path parameter, and an optional string query parameter `role` (defaulting to `None`). It should return a dictionary with both values.

Continue learning

Previous: WSGI vs ASGI

Next: Request Body & response_model

Return to Python Backend Roadmap