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
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())
{'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
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"])
{'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
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"])
{'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.