FastAPI converts a path parameter to the right type automatically, but type checking alone is not enough: /items/0 or /items/-3 still slip through even though an ID in the database is never negative or zero. With Path() and ge, le or pattern, FastAPI rejects bad values with a 422 error right at the API gateway, before your code touches the database. This applies to any endpoint with an ID-like or username-like path parameter.
The problem
Suppose you have an endpoint that fetches a product by ID, declared only as int:
from fastapi import FastAPIapp = FastAPI()@app.get("/items/{item_id}")def read_item(item_id: int): return {"item_id": item_id}
The int type only guarantees the value parses as an integer: /items/abc is blocked with a 422, but /items/0 and /items/-3 run straight into the handler. From there the code has to check by itself, or worse, carries a meaningless value down to the database and only gets a 404 back. The problem is that the API gateway has not set its own “rules of the game” for its parameter.
The solution
Declare the constraint right on the parameter with Path() and Annotated:
from typing import Annotatedfrom fastapi import FastAPI, Pathapp = FastAPI()@app.get("/items/{item_id}")def read_item( item_id: Annotated[int, Path(ge=1, le=100000, title="Product ID")],): return {"item_id": item_id}
Now /items/5 returns 200 as usual, while /items/0 gets a 422 whose loc is ["path", "item_id"] and whose message says the value must be greater than or equal to 1. Validation runs before the handler, so the database never sees a bad ID. The title parameter also shows up in the OpenAPI docs at /docs.
The same mechanism works for strings, for example a username made only of lowercase letters, digits and underscores:
@app.get("/users/{username}")def read_user( username: Annotated[str, Path(min_length=3, max_length=20, pattern="^[a-z0-9_]+$")],): return {"username": username}
With this, /users/ab is blocked for being too short, /users/Nguyen_Van is blocked for not matching the pattern, and /users/nguyen_van passes normally.
Apply it to your project
Attach Path() to every ID-like path parameter as soon as you write the route. Pick the ge/le bounds from your real data domain — do not blindly copy the 1–100000 from the example. Path() only checks shape and range; whether the ID exists is still the database layer’s job, where you return 404 when the record is not found.
Related posts
- FastAPI: Bundle Dozens of Query Params into One Pydantic Model — Centralized Validation in 5 Minutes
- FastAPI: Validate Input Properly with Pydantic Field and Custom Validators in 5 Minutes
- FastAPI: Protect Routes with a Reusable JWT/OAuth2 Dependency, No Repeated Auth Code in 5 Minutes
Frequently asked questions
How is Path() different from Query()?
Path() is for parameters inside the path, like /items/{item_id}, while Query() is for parameters after the ?, like /items?skip=0. Both accept the same constraint set — ge, le, min_length, pattern — so you do not have to learn two different ways.
How do I write Path() without Annotated?
You still can: item_id: int = Path(ge=1) is the older style and still works. The Annotated style is recommended because it separates the data type from the metadata cleanly and plays better with type-checking tools.
Can Path() replace checking whether a record exists?
No. Path() only knows whether a value is formally valid — ID 5 is a valid number, but it does not know whether ID 5 is in the database. Keep the lookup step and return 404 as usual after this validation layer.
What should I watch out for when choosing a pattern for strings?
pattern is a Python regular expression; anchor it with ^ and $ if you want to match the whole string. Test the pattern with TestClient before going to production, because a pattern that is too strict will block valid usernames too.
💬 Have you tried this in your own project? If you hit an error or know a cleaner way, leave a comment below — I read them all and will fold the best ideas into the post.
Sources
- FastAPI Documentation — Path Parameters and Numeric Validations: https://fastapi.tiangolo.com/tutorial/path-params-numeric-validations/
