Master FastAPI Path Parameters for Dynamic Data Retrieval
In this tutorial, you will learn how to implement path parameters in your FastAPI applications to create dynamic API endpoints and web pages. We will cover how to retrieve specific resources, validate input using type hints, handle HTTP exceptions with appropriate status codes, and differentiate error handling for API requests versus browser requests.
Prerequisites
- Python 3.7+ installed
- Basic understanding of Python
- Familiarity with FastAPI concepts from previous tutorials (templates, static files, basic routing)
- A development environment set up for FastAPI (e.g., using
uvicorn)
Understanding Path Parameters
Path parameters allow you to capture dynamic values directly from the URL path. Instead of fetching all resources, you can specify a unique identifier in the URL to retrieve a single, specific resource. FastAPI automatically validates these parameters based on the type hints you provide in your function signatures.
Step 1: Create an API Endpoint for a Single Resource
Let’s start by creating an API endpoint that fetches a single post based on its ID. We’ll use the existing code from the previous tutorial as a base.
- Modify your main FastAPI application file (e.g.,
main.py). - Add a new route decorator for fetching a single post. The path will include a variable part for the post ID, enclosed in curly braces:
@app.get("/api/post/{post_id}"). - Define the corresponding function (e.g.,
get_post) that acceptspost_idas an argument. - Apply a type hint to
post_id, such asint, to enable automatic validation:def get_post(post_id: int). - Inside the function, iterate through your data (e.g., a list of dictionaries) to find the post matching the provided
post_id. - If a matching post is found, return the post data.
- If no post is found, you’ll initially return a simple error message. We will refine this error handling later.
Example Code Snippet (Initial Version):
@app.get("/api/post/{post_id}")
def get_post(post_id: int):
for post in posts:
if post.get("id") == post_id:
return post
return {"error": "Post not found"}
Step 2: Implement Proper HTTP Error Handling (404 Not Found)
Returning a generic error message with a 200 OK status code when a resource is not found is not RESTful. We should return a 404 Not Found status code. FastAPI provides HTTPException and the status module for this.
- Import
HTTPExceptionandstatusfromfastapiat the top of your file:from fastapi import FastAPI, HTTPException, status. - Modify the
get_postfunction. Instead of returning a dictionary for errors, raise anHTTPException. - Use
status.HTTP_404_NOT_FOUNDfor the status code and provide a descriptive message in thedetailargument.
Example Code Snippet (Improved Error Handling):
@app.get("/api/post/{post_id}")
def get_post(post_id: int):
for post in posts:
if post.get("id") == post_id:
return post
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found")
Expert Note: Using status constants like status.HTTP_404_NOT_FOUND makes your code more readable and maintainable than using raw numbers.
Step 3: Leverage Automatic Input Validation
One of the key benefits of using type hints with path parameters is automatic validation. If a user provides input that doesn’t match the expected type, FastAPI will automatically return a 422 Unprocessable Entity error.
- Test the endpoint by navigating to a URL with a non-integer path parameter, e.g.,
/api/post/hello. - Observe the detailed JSON response indicating the validation error (e.g., “Input should be a valid integer”).
- Check the FastAPI documentation (usually at
/docs) to see how path parameters with type hints are automatically documented, including their expected types. You can even test endpoints directly from the documentation UI.
Step 4: Create a Web Page for Individual Posts
Now, let’s create a similar endpoint but one that renders an HTML template for a single post, intended for browser users.
- Create a new HTML template file (e.g.,
templates/post.html). This template should extend your base layout and display the post’s title, content, and author information. - Add a new route in your
main.pyfile for displaying individual posts. This route will also use a path parameter for the post ID:@app.get("/post/{post_id}"). - Set
include_in_schema=Falsefor this route if you don’t want it to appear in the API documentation. - The route function will need to accept the
requestobject (for rendering templates) and thepost_id: int. - Inside the function, find the post by ID, similar to the API endpoint.
- If the post is not found, raise an
HTTPExceptionwith a 404 status code. - If the post is found, use
templates.TemplateResponseto render thepost.htmltemplate, passing the found post data and a dynamic title in the context.
Example Code Snippet (Web Page Endpoint):
from fastapi.responses import HTMLResponse
from starlette.requests import Request
@app.get("/post/{post_id}")
def post_page(request: Request, post_id: int):
found_post = None
for post in posts:
if post.get("id") == post_id:
found_post = post
break
if not found_post:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Post not found")
# Limit title length for the page title
page_title = found_post.get("title")[:50]
return templates.TemplateResponse("post.html", {
"request": request,
"post": found_post,
"title": page_title
})
Step 5: Make Posts Clickable from the Homepage
To allow users to navigate to individual post pages from the homepage, we need to add links to the post titles in our templates/home.html file.
- Open your
templates/home.htmlfile. - Locate the loop that iterates over posts.
- Wrap the
{{ post.title }}element in an anchor tag (<a>). - Use FastAPI’s
url_forfunction within the anchor tag’shrefattribute to dynamically generate the URL for thepost_pageendpoint, passing the current post’s ID.
Example Code Snippet (Homepage Link):
<h2><a href="{{ url_for('post_page', post_id=post.id) }}">{{ post.title }}</a></h2>
Expert Note: Using url_for is highly recommended as it decouples your templates from your URL structure. If you change a route’s path in main.py, the links in your templates will update automatically.
Step 6: Differentiate Error Handling for API vs. Web Requests
A crucial aspect of building robust applications is handling errors appropriately for different types of clients. API clients (requesting JSON) should receive JSON error responses, while browser clients (requesting HTML) should receive user-friendly HTML error pages.
- Create a new HTML template for errors (e.g.,
templates/error.html). This template should display a status code and a user-friendly message. - Import necessary components:
RequestValidationErrorfromfastapi.exceptions,JSONResponsefromfastapi.responses, andHTTPExceptionfromstarlette.exceptions(aliased asStarletteHTTPExceptionto avoid conflict with FastAPI’sHTTPException). - Add exception handlers to your FastAPI app using the
@app.exception_handler()decorator. - Create a handler for
StarletteHTTPException. Inside this handler: - Check if the request path starts with
"/api". - If it does, return a
JSONResponsewith the appropriate status code and detail message. - If it doesn’t, return a
TemplateResponseusing yourerror.htmltemplate, passing the request, status code, message, and title. Ensure the correct HTTP status code is set for the response itself. - Add a separate handler for
RequestValidationError. This handler should: - For API requests (path starting with
"/api"), return aJSONResponsewith status code 422 and the detailed validation errors from the exception. - For web requests, return a
TemplateResponseusing theerror.htmltemplate, indicating an invalid request, and set the status code to 422.
Example Code Snippet (Exception Handlers):
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse, Response
from starlette.exceptions import HTTPException as StarletteHTTPException
@app.exception_handler(StarletteHTTPException)
def http_exception_handler(request: Request, exc: StarletteHTTPException):
message = exc.detail
status_code = exc.status_code
if request.url.path.startswith("/api"):
return JSONResponse(status_code=status_code, content={"detail": message})
else:
return templates.TemplateResponse("error.html", {
"request": request,
"status_code": status_code,
"message": message,
"title": f"Error {status_code}"
}, status_code=status_code)
@app.exception_handler(RequestValidationError)
def validation_exception_handler(request: Request, exc: RequestValidationError):
status_code = status.HTTP_422_UNPROCESSABLE_ENTITY
if request.url.path.startswith("/api"):
# For APIs, return detailed validation errors
return JSONResponse(status_code=status_code, content={"detail": exc.errors(), "body": exc.body})
else:
# For web pages, return a generic invalid request message
return templates.TemplateResponse("error.html", {
"request": request,
"status_code": status_code,
"message": "The request was invalid. Please check your input.",
"title": f"Invalid Request ({status_code})"
}, status_code=status_code)
Expert Note: Catching StarletteHTTPException is important because FastAPI is built on Starlette. This ensures that even errors originating from Starlette itself (like basic 404s for undefined routes) are handled by your custom logic.
Testing Your Implementation
- API 404: Navigate to
/api/post/99(assuming post 99 doesn’t exist). You should receive a JSON response with status code 404 and “Post not found”. - Web 404: Navigate to
/post/99. You should see yourerror.htmlpage displaying a 404 error. - Undefined Route (Web): Navigate to a non-existent web route like
/nonexistent. You should see yourerror.htmlpage. - API Validation Error: Navigate to
/api/post/hello. You should receive a JSON response with status code 422 and detailed validation errors. - Web Validation Error: Navigate to
/post/hello. You should see yourerror.htmlpage displaying a “Invalid Request” message with status code 422.
Conclusion
You have now successfully implemented path parameters in FastAPI, enabling dynamic resource retrieval. You’ve learned to validate input using type hints, handle HTTP exceptions with correct status codes, and create sophisticated error handling that distinguishes between API and web clients. This provides a solid foundation for building more complex and user-friendly applications. In the next steps, you will explore Pydantic models for advanced data validation and integrate with databases.
Source: Python FastAPI Tutorial (Part 3): Path Parameters – Validation and Error Handling (YouTube)