Skip to content
OVEX TECH
Education & E-Learning

Build Dynamic FastAPI APIs with Path Parameters

Build Dynamic FastAPI APIs with Path Parameters

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.

  1. Modify your main FastAPI application file (e.g., main.py).
  2. 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}").
  3. Define the corresponding function (e.g., get_post) that accepts post_id as an argument.
  4. Apply a type hint to post_id, such as int, to enable automatic validation: def get_post(post_id: int).
  5. Inside the function, iterate through your data (e.g., a list of dictionaries) to find the post matching the provided post_id.
  6. If a matching post is found, return the post data.
  7. 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.

  1. Import HTTPException and status from fastapi at the top of your file: from fastapi import FastAPI, HTTPException, status.
  2. Modify the get_post function. Instead of returning a dictionary for errors, raise an HTTPException.
  3. Use status.HTTP_404_NOT_FOUND for the status code and provide a descriptive message in the detail argument.

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.

  1. Test the endpoint by navigating to a URL with a non-integer path parameter, e.g., /api/post/hello.
  2. Observe the detailed JSON response indicating the validation error (e.g., “Input should be a valid integer”).
  3. 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.

  1. 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.
  2. Add a new route in your main.py file for displaying individual posts. This route will also use a path parameter for the post ID: @app.get("/post/{post_id}").
  3. Set include_in_schema=False for this route if you don’t want it to appear in the API documentation.
  4. The route function will need to accept the request object (for rendering templates) and the post_id: int.
  5. Inside the function, find the post by ID, similar to the API endpoint.
  6. If the post is not found, raise an HTTPException with a 404 status code.
  7. If the post is found, use templates.TemplateResponse to render the post.html template, 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.

  1. Open your templates/home.html file.
  2. Locate the loop that iterates over posts.
  3. Wrap the {{ post.title }} element in an anchor tag (<a>).
  4. Use FastAPI’s url_for function within the anchor tag’s href attribute to dynamically generate the URL for the post_page endpoint, 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.

  1. Create a new HTML template for errors (e.g., templates/error.html). This template should display a status code and a user-friendly message.
  2. Import necessary components: RequestValidationError from fastapi.exceptions, JSONResponse from fastapi.responses, and HTTPException from starlette.exceptions (aliased as StarletteHTTPException to avoid conflict with FastAPI’s HTTPException).
  3. Add exception handlers to your FastAPI app using the @app.exception_handler() decorator.
  4. Create a handler for StarletteHTTPException. Inside this handler:
    • Check if the request path starts with "/api".
    • If it does, return a JSONResponse with the appropriate status code and detail message.
    • If it doesn’t, return a TemplateResponse using your error.html template, passing the request, status code, message, and title. Ensure the correct HTTP status code is set for the response itself.
  5. Add a separate handler for RequestValidationError. This handler should:
    • For API requests (path starting with "/api"), return a JSONResponse with status code 422 and the detailed validation errors from the exception.
    • For web requests, return a TemplateResponse using the error.html template, 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 your error.html page displaying a 404 error.
  • Undefined Route (Web): Navigate to a non-existent web route like /nonexistent. You should see your error.html page.
  • 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 your error.html page 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)

Leave a Reply

Your email address will not be published. Required fields are marked *

Written by

John Digweed

1,289 articles

Life-long learner.