Course topics

By WebNest Studio

Python Tutorial

Path and Query Parameters in FastAPI

Most endpoints need input. The simplest inputs come from the URL itself: path parameters identify a specific resource (/books/42) and query parameters filter, sort or paginate (/books?author=asha&page=2).

In FastAPI you declare both as ordinary function parameters. The type hints do the work: book_id: int converts "42" to 42 and rejects "abc" with a clear 422 error. This lesson covers path parameters, enums, query parameters with defaults and optional values, lists, and declarative validation with Path() and Query().

Path Parameters

Put the name in braces in the path and add a parameter with the same name: @app.get("/books/{book_id}") with def get_book(book_id: int). An Enum type restricts a parameter to fixed values and shows them as a dropdown in /docs. The special form {file_path:path} captures the rest of the URL, including slashes. Order matters: declare fixed paths such as /users/me before /users/{user_id}, because the first match wins.

Query Parameters

Any function parameter that is not in the path is a query parameter. With a default value it is optional (page: int = 1); without one it is required; str | None = None makes it truly optional. bool parameters accept true/false, 1/0, yes/no and on/off. A list[str] query parameter collects repeated keys such as ?tag=new&tag=sale.

Validation with Annotated, Path and Query

Add rules with Annotated[type, Query(...)] or Annotated[type, Path(...)]: numeric limits gt, ge, lt, le; string limits min_length, max_length and pattern; plus alias, description and deprecated for the docs. Invalid input never reaches your function — FastAPI answers with 422 Unprocessable Content and a JSON list describing exactly which parameter failed and why.

Examples

Path parameters, type conversion, enums and path captures

Python
from enum import Enum
from fastapi import FastAPI
from fastapi.testclient import TestClient

app = FastAPI()
BOOKS = {1: "Python Basics", 2: "Deep Python", 3: "SQL for Beginners"}

class Category(str, Enum):
    python = "python"
    sql = "sql"

@app.get("/books/{book_id}")
def get_book(book_id: int):                     # "2" in the URL becomes the int 2
    return {"id": book_id, "title": BOOKS.get(book_id)}

@app.get("/categories/{category}")
def by_category(category: Category):
    return {"category": category, "is_python": category is Category.python}

@app.get("/files/{file_path:path}")
def read_file(file_path: str):
    return {"path": file_path}

client = TestClient(app)
print(client.get("/books/2").json())
bad = client.get("/books/abc")
print(bad.status_code, bad.json()["detail"][0]["msg"])
print(client.get("/categories/sql").json())
print(client.get("/categories/java").status_code)
print(client.get("/files/reports/2026/sales.csv").json())
Output
{'id': 2, 'title': 'Deep Python'}
422 Input should be a valid integer, unable to parse string as an integer
{'category': 'sql', 'is_python': False}
422
{'path': 'reports/2026/sales.csv'}

Query parameters: required, optional, defaults, booleans and lists

Python
from fastapi import FastAPI, Query
from fastapi.testclient import TestClient
from typing import Annotated

app = FastAPI()

@app.get("/books")
def list_books(
    author: str | None = None,          # optional
    page: int = 1,                      # optional with a default
    in_stock: bool = False,
    tag: Annotated[list[str] | None, Query()] = None,   # ?tag=a&tag=b
):
    return {"author": author, "page": page, "in_stock": in_stock, "tags": tag}

@app.get("/convert")
def convert(amount: float, currency: str):   # both required
    rates = {"usd": 0.012, "eur": 0.011}
    return {"inr": amount, currency: round(amount * rates[currency], 2)}

client = TestClient(app)
print(client.get("/books").json())
print(client.get("/books?author=asha&page=3&in_stock=yes&tag=new&tag=sale").json())
print(client.get("/convert", params={"amount": 1000, "currency": "usd"}).json())
missing = client.get("/convert?amount=1000")
print(missing.status_code, missing.json()["detail"][0]["loc"], missing.json()["detail"][0]["msg"])
Output
{'author': None, 'page': 1, 'in_stock': False, 'tags': None}
{'author': 'asha', 'page': 3, 'in_stock': True, 'tags': ['new', 'sale']}
{'inr': 1000.0, 'usd': 12.0}
422 ['query', 'currency'] Field required

Declarative validation with Annotated, Query and Path

Python
from typing import Annotated
from fastapi import FastAPI, Path, Query
from fastapi.testclient import TestClient

app = FastAPI()

@app.get("/search")
def search(
    q: Annotated[str, Query(min_length=2, max_length=50, description="Search text")],
    page: Annotated[int, Query(ge=1)] = 1,
    size: Annotated[int, Query(ge=1, le=100)] = 10,
    sort: Annotated[str, Query(pattern="^(price|title|rating)$")] = "title",
):
    return {"q": q, "page": page, "size": size, "sort": sort}

@app.get("/books/{book_id}/reviews")
def reviews(book_id: Annotated[int, Path(gt=0, title="Book id")], limit: int = 5):
    return {"book_id": book_id, "limit": limit}

client = TestClient(app)
print(client.get("/search?q=python&page=2&sort=price").json())
for url in ["/search?q=p", "/search?q=python&size=500", "/search?q=python&sort=name", "/books/0/reviews"]:
    error = client.get(url).json()["detail"][0]
    print(url, "->", error["msg"])
Output
{'q': 'python', 'page': 2, 'size': 10, 'sort': 'price'}
/search?q=p -> String should have at least 2 characters
/search?q=python&size=500 -> Input should be less than or equal to 100
/search?q=python&sort=name -> String should match pattern '^(price|title|rating)$'
/books/0/reviews -> Input should be greater than 0

Common Mistakes

  • Declaring /users/{user_id} before /users/me, so "me" is treated as an id and fails validation.
  • Using a mutable default like tags: list = [] instead of Annotated[list[str] | None, Query()] = None.
  • Validating inputs by hand inside the function instead of using Query/Path constraints.
  • Putting identifiers in query strings and filters in paths — paths identify, queries refine.
  • Forgetting that every path and query value arrives as text; without a type hint it stays a string.

Key Points to Remember

  • Path parameters: {name} in the path plus a matching function parameter.
  • Other parameters are query parameters; defaults make them optional.
  • Type hints convert and validate: int, float, bool, Enum, list[str].
  • Annotated with Query()/Path() adds limits such as ge, le, min_length and pattern.
  • Invalid input gets an automatic 422 response describing the problem.

Practice the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

Use your local project environment for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.