FastAPI Path, Query, and Body Parameters in Python
python fastapi path query parameters and request body: Learn how to declare and combine path, query, and body parameters in FastAPI with validation, defaults, and Pyda...
This article covers the essential patterns for working with python fastapi path query parameters and request body in a FastAPI application. You'll see how to declare each parameter type, how to combine them in a single endpoint, and how to apply validation rules that keep your API robust.
Declaring Path Parameters
Path parameters are part of the URL path itself. In FastAPI, you declare them by placing a placeholder in the route path and adding a function parameter with the same name.
from fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id: int): return {"item_id": item_id}
Here, item_id is a path parameter. FastAPI reads the value from the URL, converts it to the declared type (int), and passes it to the function. If the value cannot be converted, FastAPI returns a 422 validation error.
Path parameters are always required. There is no way to give a default value because the URL path must contain the segment. You can use Path() from FastAPI to add extra validation, as shown later.
Declaring Query Parameters
Query parameters come from the URL query string, the part after ?. They are declared as function parameters that are not part of the path.
@app.get("/items/") async def list_items(skip: int = 0, limit: int = 10): return {"skip": skip, "limit": limit}
Here, skip and limit are query parameters. They are optional because they have default values. If a client omits them, the defaults are used. If a client sends ?skip=20&limit=5, FastAPI parses and validates them.
Query parameters can also be made required by omitting the default:
@app.get("/items/") async def search_items(q: str): return {"query": q}
Now q is required. A request without ?q=... will result in a 422 error.
Combining Path and Query Parameters
You can use both path and query parameters in the same endpoint. FastAPI distinguishes them by their position: parameters that match a placeholder in the path are path parameters; all others are treated as query parameters.
@app.get("/users/{user_id}/items") async def get_user_items(user_id: int, skip: int = 0, limit: int = 10): return {"user_id": user_id, "skip": skip, "limit": limit}
The order of function parameters does not matter. FastAPI uses the type hints and the route definition to decide where each value comes from.
Declaring a Request Body with Pydantic
For POST, PUT, or PATCH requests, the request body typically contains structured data. FastAPI uses Pydantic models to declare the expected shape of that body.
from pydantic import BaseModel class Item(BaseModel): name: str price: float is_offer: bool = False @app.post("/items/") async def create_item(item: Item): return {"item_name": item.name, "price": item.price}
When you declare a parameter with a Pydantic model type, FastAPI treats it as the request body. It parses the JSON payload, validates it against the model, and gives you an Item instance. If the body is missing or invalid, FastAPI returns a 422 error with details about the failure.
You can also use Body to embed a single value in the body, but a Pydantic model is the most common and maintainable approach.
Mixing Path, Query, and Body Parameters
A single endpoint can accept all three types. FastAPI resolves each parameter based on its declaration.
@app.put("/items/{item_id}") async def update_item(item_id: int, item: Item, q: str = None): return {"item_id": item_id, "item": item, "q": q}
Here, item_id is a path parameter, item is the request body, and q is an optional query parameter. FastAPI knows that item is a Pydantic model and therefore should come from the body. The other scalar types are treated as query parameters unless they match a path placeholder.
If you need to send a single value in the body rather than a JSON object, you can use Body():
from fastapi import Body @app.post("/items/") async def create_item(item_id: int = Body(...)): return {"item_id": item_id}
This tells FastAPI to expect a raw JSON value in the body, not an object.
Adding Validation with Path, Query, and Body
FastAPI provides Path, Query, and Body functions to add constraints like minimum length, numeric bounds, and regex patterns.
from fastapi import Path, Query @app.get("/items/{item_id}") async def read_item( item_id: int = Path(..., ge=1), q: str = Query(None, max_length=50) ): return {"item_id": item_id, "q": q}
Path(..., ge=1) makes item_id required and enforces that it is greater than or equal to 1. Query(None, max_length=50) makes q optional but limits its length.
For body fields, you can use Pydantic's Field to add validation directly in the model.
from pydantic import BaseModel, Field class Item(BaseModel): name: str = Field(..., min_length=1, max_length=100) price: float = Field(..., ge=0.0)
This keeps validation logic in one place and prevents the same checks from being duplicated across request handlers.
Common Mistakes and Edge Cases
One common mistake is forgetting that path parameters are always strings in the URL. Even if you declare item_id: int, the raw URL value is a string. FastAPI handles the conversion for you, but if you pass the value to another function that expects a string, you need to be explicit.
Another issue is ordering when you have both path and query parameters with the same name. FastAPI will treat the one that matches the path placeholder as the path parameter, and the other as a query parameter, but this is confusing and should be avoided.
Also, when you use Body with a Pydantic model, you cannot also use Body for a scalar parameter in the same endpoint unless you use embed=True. For example:
@app.put("/items/{item_id}") async def update_item( item_id: int, item: Item = Body(..., embed=True), q: str = None ): ...
With embed=True, the body must be {"item": {...}} instead of the model fields directly. This is useful when you need to send additional metadata alongside the model.
Handling Validation Errors Gracefully
FastAPI automatically returns a 422 Unprocessable Entity response when validation fails. The response body contains a list of errors with the field location and the reason. You can customize this by adding an exception handler.
from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): return JSONResponse( status_code=422, content={"detail": exc.errors(), "body": exc.body} )
This handler gives you access to the original request body and the validation errors, which can be useful for logging or returning a custom error format.
For production, consider logging the validation errors and returning a concise message to the client. Avoid exposing internal details unless your API is internal.