Skip to main content

FastAPI Path Parameters

Introduction

Path parameters are a crucial part of building dynamic APIs. They allow you to capture values from the URL path and use them in your API functions. In FastAPI, path parameters are easy to define and come with built-in validation, documentation, and type conversion capabilities.

When developing web applications, you'll often need to retrieve specific resources based on identifiers in the URL. For example, you might want to fetch a user's profile by their ID (/users/123) or get details about a product using its slug (/products/wireless-headphones). This is where path parameters shine.

In this tutorial, we'll explore how to work with path parameters in FastAPI, from basic usage to more advanced scenarios.

Basic Path Parameters

Defining a Path Parameter

To define a path parameter in FastAPI, you use curly braces {} in your route path and include the parameter with the same name in your function:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def read_user(user_id):
return {"user_id": user_id}

When you make a request to /users/123, FastAPI automatically captures 123 as the user_id parameter and passes it to your function.

Type Validation

One of FastAPI's most powerful features is automatic type validation. By adding type hints to your path parameters, FastAPI will:

  • Convert the parameter to the specified type
  • Validate the data
  • Document the parameter type in the OpenAPI schema
python
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def read_user(user_id: int):
return {"user_id": user_id, "user_id_type": type(user_id).__name__}

Now, if you access /users/123, FastAPI will convert 123 to an integer. However, if you try /users/abc, you'll get a validation error since abc cannot be converted to an integer.

Sample output for a valid request:

json
{
"user_id": 123,
"user_id_type": "int"
}

Multiple Path Parameters

You can define multiple path parameters in a single route:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}/posts/{post_id}")
async def get_user_post(user_id: int, post_id: int):
return {
"user_id": user_id,
"post_id": post_id
}

This route would match URLs like /users/1/posts/42.

Path Parameters with Predefined Values

Sometimes you want to restrict path parameters to a set of predefined values. You can use Python's Enum class for this:

python
from enum import Enum
from fastapi import FastAPI

class ModelName(str, Enum):
resnet = "resnet"
alexnet = "alexnet"
yolo = "yolo"

app = FastAPI()

@app.get("/models/{model_name}")
async def get_model(model_name: ModelName):
if model_name is ModelName.resnet:
return {"model_name": model_name, "message": "Deep ResNet model"}

if model_name is ModelName.alexnet:
return {"model_name": model_name, "message": "AlexNet model"}

return {"model_name": model_name, "message": "YOLO object detection model"}

Now, the model_name parameter will only accept values defined in the ModelName enum, and your API documentation will show the available options.

Path Parameter Validation

FastAPI provides more advanced validation through Pydantic. For example, you can set constraints on integers:

python
from fastapi import FastAPI, Path

app = FastAPI()

@app.get("/items/{item_id}")
async def read_item(
item_id: int = Path(title="The ID of the item to get", ge=1, le=1000)
):
return {"item_id": item_id}

In this example:

  • title provides a description for the documentation
  • ge=1 specifies that item_id must be greater than or equal to 1
  • le=1000 ensures item_id is less than or equal to 1000

Path Parameters with Regular Expressions

You can use regular expressions to define specific patterns for path parameters:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/files/{file_path:path}")
async def read_file(file_path: str):
return {"file_path": file_path}

The :path suffix allows the parameter to match paths that contain forward slashes, which is useful for file paths.

Similarly, you can use custom regex patterns:

python
@app.get("/users/{username:str}")
async def read_username(username: str):
return {"username": username}

@app.get("/license-plates/{license:regexp([A-Z]{2}-[0-9]{3}-[A-Z]{2})}")
async def read_license_plate(license: str):
return {"license": license}

Order Matters

When you have multiple routes with path parameters, FastAPI evaluates them in the order they're defined. This is important when you have routes that could potentially match the same URL pattern:

python
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/me")
async def read_current_user():
return {"user_id": "the current user"}

@app.get("/users/{user_id}")
async def read_user(user_id: str):
return {"user_id": user_id}

In this example, if someone accesses /users/me, the first route will match because it's defined first. If the order was reversed, the second route would match /users/me and interpret me as the user_id.

Real-world Example: Building a Blog API

Let's create a simple blog API with path parameters:

python
from fastapi import FastAPI, HTTPException, Path
from typing import Dict, List
from enum import Enum

app = FastAPI()

# Sample data
blogs = {
1: {"id": 1, "title": "FastAPI Introduction", "content": "FastAPI is awesome!"},
2: {"id": 2, "title": "Path Parameters", "content": "Learn about path parameters in FastAPI"}
}

class SortOptions(str, Enum):
id = "id"
title = "title"
newest = "newest"

@app.get("/blogs/")
async def list_blogs(skip: int = 0, limit: int = 10):
"""List all blogs with pagination."""
return list(blogs.values())[skip:skip+limit]

@app.get("/blogs/{blog_id}")
async def get_blog(blog_id: int = Path(..., title="The ID of the blog to retrieve", ge=1)):
"""Get a specific blog by ID."""
if blog_id not in blogs:
raise HTTPException(status_code=404, detail="Blog not found")
return blogs[blog_id]

@app.get("/blogs/sort/{sort_by}")
async def sort_blogs(sort_by: SortOptions):
"""Sort blogs by different criteria."""
blog_list = list(blogs.values())

if sort_by is SortOptions.id:
return sorted(blog_list, key=lambda x: x["id"])
elif sort_by is SortOptions.title:
return sorted(blog_list, key=lambda x: x["title"])
elif sort_by is SortOptions.newest:
# Assuming we had a 'created_at' field
# return sorted(blog_list, key=lambda x: x["created_at"], reverse=True)
return blog_list

@app.get("/categories/{category}/blogs/{blog_id}")
async def get_blog_by_category(category: str, blog_id: int):
"""Get a blog by category and ID."""
# In a real application, we would filter by category
if blog_id not in blogs:
raise HTTPException(status_code=404, detail="Blog not found")
return {"category": category, "blog": blogs[blog_id]}

In this example, we've created several endpoints that demonstrate different aspects of path parameters:

  1. A basic blog retrieval by ID
  2. Path parameters with validation
  3. Enum-based path parameters for sorting
  4. Multiple path parameters for filtering by category and ID

Summary

Path parameters in FastAPI provide a powerful way to create dynamic API endpoints. By leveraging FastAPI's type hints and validation capabilities, you can build robust APIs that are both self-documenting and secure.

Key points to remember:

  1. Use curly braces {} to define path parameters in route paths
  2. Add type hints to enable automatic conversion and validation
  3. Use Pydantic's Path() for additional validation
  4. Consider the order of route definitions to avoid conflicts
  5. Path parameters can be combined with query parameters for flexible APIs

Practice Exercises

  1. Create an API endpoint that accepts a user ID and returns user information
  2. Build a "calculator" API with endpoints for basic operations (add, subtract, etc.) that take numerical parameters
  3. Implement an API for a library that lets you fetch books by ID, ISBN, or title
  4. Create an API for a file system that accepts file paths as parameters
  5. Design a RESTful API for a blog with proper path parameters for posts, categories, and authors

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)