FastAPI Path & Query Validation
Learn FastAPI path and query parameter validation with hands-on examples, best practices, and troubleshooting tips.
Focus: FastAPI path and query parameter validation
You've built FastAPI endpoints that accept data, but what happens when a user sends a path parameter like /items/abc when your code expects an integer? Or when they omit a required query parameter? Without validation, your API silently breaks, returns confusing errors, or — worse — crashes. In this lesson, you'll master FastAPI path and query parameter validation, turning raw request data into tested, typed, and predictable inputs. You'll add constraints, custom error messages, and even regex patterns, so your endpoints become self-documenting and resilient against bad data.
The problem this lesson solves
Imagine you have an endpoint GET /items/{item_id}. A client calls /items/42 and it works. But then someone sends /items/not-a-number. Without validation, FastAPI doesn't know what to do — your code might try to compare a string with an integer, raise a ValueError, or worse, expose a 500 Internal Server Error. The same chaos happens with query parameters: GET /search?q= with an empty string, or GET /users?limit=-5 which could try to slice a list with a negative number.
Unvalidated parameters are the root of most API bugs. They cause:
- Unexpected runtime exceptions that crash your process.
- Inconsistent error responses that confuse frontend developers.
- Security vulnerabilities, like path traversal when you use user input in file paths.
- A poor developer experience — consumers of your API have to guess what's allowed.
FastAPI path and query parameter validation solves this by declaring rules directly in your function signature. You get automatic type coercion, range checks, pattern matching, and readable error messages — all without writing a single if statement.
Core concept / mental model
Think of your API endpoint as a factory door. Each parameter (path or query) is a package that arrives at the door. Without validation, you'd accept any package, even ones that are broken or dangerous. With validation, you install a smart sensor that checks each package: is it the right type? Is it within the allowed range? Does it match a pattern? If not, the sensor rejects it with a clear message.
In FastAPI, this sensor is built from Python type hints plus optional validation metadata from the Path and Query classes (imported from fastapi). The key idea:
- Type hints (like
int,str,bool) tell FastAPI the expected data type and enable automatic conversion. Path()andQuery()let you add constraints — minimum/maximum values, regex patterns, descriptions, and aliases.- The validation happens before your function body runs, so you never see invalid data.
Here's a mental diagram:
HTTP Request ──> FastAPI parses URL ──> Validate each param ──> Call your function ──> Return response
|
└── If invalid: return 422 Unprocessable Entity (with detailed errors)
How it works step by step
Let's break down the mechanics of FastAPI path and query parameter validation. Here's a minimal example without explicit validation (only type hints):
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str = "default"):
return {"item_id": item_id, "q": q}
What happens when you request /items/5?q=hello?
- FastAPI extracts
5from the path andhellofrom the query string. - It checks the type hint:
item_idmust be anint— it converts the string"5"to the integer5. qis astrwith a default, so it's fine.- Your function runs. If someone requests
/items/abc, FastAPI returns a 422 error with a message like"value is not a valid integer"before your function ever executes.
Now add constraints with Path and Query:
from fastapi import FastAPI, Path, Query
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(
item_id: int = Path(..., title="The ID of the item", ge=1, le=1000),
q: str = Query(None, max_length=50, pattern="^[a-zA-Z0-9_]*$"),
limit: int = Query(10, ge=1, le=100)
):
return {"item_id": item_id, "q": q, "limit": limit}
Here's what each piece does:
Path(..., ge=1, le=1000)—...makes the parameter required;ge(greater than or equal) andle(less than or equal) enforce a numeric range.Query(None, max_length=50, pattern="...")—Nonemakesqoptional;max_lengthcaps its length, andpatternenforces a regex.Query(10, ge=1, le=100)— a required query parameter with a default of 10, and validates it's between 1 and 100.
Step-by-step flow with validation
- Declare parameters in your function signature with type hints and
Path/Querywrappers. - FastAPI reads the OpenAPI schema generated from your annotations to know what to validate.
- On each request, FastAPI parses the URL and query string, then applies every constraint.
- If any check fails, FastAPI immediately returns a 422 Unprocessable Entity with a JSON body listing each error (e.g.,
"ge"for min,"pattern"for regex). - If all pass, your function runs with clean, typed data.
Hands-on walkthrough
Let's build a realistic API that uses both path and query validation. We'll make an endpoint that returns a paginated list of items based on a category path parameter and a search query.
Example 1: Basic path and query validation
from fastapi import FastAPI, Path, Query
app = FastAPI()
# Mock data
ITEMS = {
"electronics": ["laptop", "phone", "camera"],
"books": ["python", "fastapi", "sql"]
}
@app.get("/categories/{category_name}/items")
def get_items(
category_name: str = Path(..., title="Category name", min_length=2, max_length=20, pattern="^[a-z]+$"),
query: str = Query(None, description="Search term", max_length=50),
skip: int = Query(0, ge=0, description="Number of items to skip"),
limit: int = Query(10, ge=1, le=50, description="Maximum items to return")
):
items = ITEMS.get(category_name, [])
if query:
items = [item for item in items if query.lower() in item.lower()]
return {"category": category_name, "items": items[skip:skip+limit]}
Run this with uvicorn main:app --reload and try these requests:
GET /categories/electronics/items?query=lapt&limit=5→ returns{"category": "electronics", "items": ["laptop"]}GET /categories/Electronics/items→ 422 error because pattern^[a-z]+$rejects uppercase.GET /categories/books/items?limit=200→ 422 error becausele=50is violated.
Example 2: Using aliases and defaults
Sometimes the query parameter name in the URL doesn't match your Python variable. Use alias to map them.
from fastapi import FastAPI, Query, Path
app = FastAPI()
@app.get("/items/{item_id}")
def get_item(
item_id: int = Path(..., gt=0, title="The ID of the item"),
verbose: bool = Query(False, alias="verbose_mode", description="Return extra details")
):
if verbose:
return {"item_id": item_id, "details": "This is a verbose response"}
return {"item_id": item_id}
Now GET /items/1?verbose_mode=true sets verbose to True. Note that verbose is a boolean — FastAPI validates that the value is true/false (case-insensitive) and converts it.
Example 3: Validation with enums (advanced)
For categorical parameters, use Python's enum to limit allowed values.
from enum import Enum
from fastapi import FastAPI, Path, Query
class OrderStatus(str, Enum):
pending = "pending"
shipped = "shipped"
delivered = "delivered"
app = FastAPI()
@app.get("/orders/{order_id}")
def get_order(
order_id: int = Path(..., gt=0),
status: OrderStatus = Query(OrderStatus.pending, description="Filter by status")
):
return {"order_id": order_id, "status": status.value}
Calling /orders/123?status=cancelled returns a 422 error because cancelled isn't a valid enum value. The OpenAPI docs render a dropdown for status in the interactive UI — validation and documentation in one.
Expected output from Example 1 (when valid)
$ curl "http://localhost:8000/categories/books/items?query=fast&limit=5"
{"category":"books","items":["fastapi"]}
Compare options / when to choose what
FastAPI gives you several ways to validate parameters. Here's how to decide:
| Approach | When to use | Pros | Cons |
|---|---|---|---|
Plain type hints (e.g., int) |
Quick prototypes, no complex rules | Minimal code, automatic conversion | No range/length checks |
Path() / Query() with constraints |
Production APIs, strict contracts | Rich validation, OpenAPI docs, clear errors | More verbose |
Enum types |
Fixed set of allowed values | Self-documenting, type safety | Requires extra class definition |
| Pydantic models (covered later) | Complex combinations of parameters | Full model validation, reusable | Overkill for simple params |
When to choose what:
- Start with plain type hints — FastAPI already validates types.
- Add
Path/Queryconstraints as soon as you have business rules (e.g.,limitmust be ≤ 100). - Use
Enumfor status fields or category names that should be a fixed set. - For complex nested query parameters (e.g., filters as a JSON object), move to Pydantic models — we'll cover that later in the track.
Troubleshooting & edge cases
Even with FastAPI's magic, things go wrong. Here are common pitfalls:
1. Path parameter order matters
In Python, non-default arguments can't follow default arguments. In FastAPI, Path parameters without a default (using ...) must come before Query parameters with defaults either in the function signature? Actually, in Python, parameters with defaults must be declared after those without defaults. So this is invalid:
# WRONG - SyntaxError
def read_item(q: str = Query(None), item_id: int = Path(...)): ...
Fix: Declare required parameters first.
# CORRECT
def read_item(item_id: int = Path(...), q: str = Query(None)): ...
2. Path parameters can't have defaults
FastAPI raises an error if you try to give a path parameter a default value (other than ...). Path parameters are always required because they're part of the URL.
3. Query parameter with ... makes it required
If you want a required query parameter, use Query(...). But if it's optional, use None as the default. Mixing these up leads to unexpected 422s.
4. Regex pattern gotchas
- Use raw strings (
r"^[a-z]+$") to avoid escaping issues. - The regex is matched against the entire string by default? Actually, FastAPI uses
re.search— sopattern="[a-z]"would match any string containing a lowercase letter. To anchor, use^...$as we did. - In FastAPI's OpenAPI schema, the pattern is compiled with JavaScript regex syntax — Python's regex is similar but not identical. Test carefully.
5. Boolean query parameters
FastAPI accepts true, false, 1, 0, yes, no, etc., and converts to bool. But if you send "True" (capital T), it may fail. Stick to lowercase in URLs.
6. 422 vs 400 errors
FastAPI returns 422 Unprocessable Entity for validation failures. Some clients expect 400. You can customize the exception handler, but 422 is the standard for FastAPI and OpenAPI. Keep it unless you have a strong reason to change.
What you learned & what's next
In this lesson, you learned how to apply FastAPI path and query parameter validation effectively. You can now:
- Use type hints to ensure parameters are the correct data type.
- Add constraints like
ge,le,max_length, andpatternwithPathandQuery. - Handle required vs optional parameters using defaults and
.... - Use
Enumfor categorical validation. - Understand error responses (422) and common pitfalls.
This is a huge step toward building robust, production-ready APIs. Next in the FastAPI Backend Development track, you'll dive into Pydantic models for request bodies — where you'll validate complex JSON payloads with even richer rules. You'll reuse the validation mindset you just built. Keep experimenting: add validation to your existing endpoints, and check how the interactive docs reflect your constraints. You're one step closer to deploying a gracefully validated API.
Practice recap
Open your existing FastAPI project and add validation to at least two endpoints: one with a path parameter (e.g., enforce item_id is positive and ≤ 1000) and one with query params (e.g., limit between 1 and 50, search no longer than 50 chars). Test invalid inputs and observe the 422 error structure. Then move to the next lesson on Pydantic models to validate request bodies.
Common mistakes
- Using
Query(10, ge=1)but forgettingle— your API accepts 1000 even if you meant a max of 100. - Putting a default value on a path parameter (e.g.,
item_id: int = 1), which causes a FastAPI error. - Applying a regex pattern without anchors (
^and$), leading to partial matches that pass validation unexpectedly. - Mixing up order in function signature: required parameters must come before optional ones, or Python raises a SyntaxError.
- Assuming a query parameter marked with
Query(...)is optional — it's required, causing 422 errors if omitted.
Variations
- Use condensed inline syntax like
item_id: int = Path(ge=1)instead of the fullPath(..., ...)form. - Leverage
Annotated(fromtyping) for cleaner separation of validation and defaults in larger codebases. - Instead of
Enum, use aLiteraltype fromtypingto restrict values to a fixed set.
Real-world use cases
- E-commerce API: validate product IDs are positive integers and paginate with
limitandskipquery params. - Blog platform: ensure category slugs are lowercase alphanumeric in the path, and search queries are under 100 chars.
- Analytics dashboard: require a date range as query parameters, validating ISO format and start before end.
Key takeaways
- FastAPI validates path and query parameters before your function runs, returning 422 on failure.
- Type hints alone give you data type validation and conversion — add
Path()andQuery()for stricter rules. - Use
ge,le,min_length,max_length, andpatternto enforce business constraints. - Path parameters are always required; use
...for required query parameters andNonefor optional ones. Enumtypes turn parameter validation into a self-documenting dropdown in the interactive API docs.- Order matters in function signatures: required (no default) parameters first, then optional with defaults.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.