FastAPI Path Parameters
Path parameters are a fundamental concept in web API development that allow you to create dynamic routes. They capture values from specific parts of the URL path and make them available to your API functions. In this tutorial, we'll explore how to use path parameters in FastAPI to build flexible and intuitive APIs.
What are Path Parameters?
Path parameters (also called URL parameters) are variable parts of a URL path that change based on the resource being accessed. For example, in the URL /users/123
, "123" could be a path parameter that represents a user ID.
Path parameters let you:
- Create dynamic routes that respond differently based on the values in the URL
- Design RESTful APIs that follow resource-based URL conventions
- Capture important identifiers directly from the URL path
Defining Path Parameters in FastAPI
In FastAPI, path parameters are defined by placing variable names inside curly braces {}
in your path string. The parameter values will automatically be passed to your route function as arguments.
Let's create a basic example:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id):
return {"item_id": item_id}
In this example, when a user visits /items/42
, FastAPI will:
- Match the route pattern
/items/{item_id}
- Extract "42" as the value for
item_id
- Call the
read_item
function withitem_id="42"
- Return
{"item_id": "42"}
as a JSON response
Type Conversion and Validation
By default, all path parameters are treated as strings. However, FastAPI can automatically convert parameters to other types, which provides automatic validation. Let's modify our example:
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/{item_id}")
def read_item(item_id: int):
return {"item_id": item_id, "item_type": type(item_id).__name__}
Now if you access /items/42
, FastAPI will:
- Convert "42" to the integer
42
- Validate that it can be converted to an integer
- Call
read_item
withitem_id=42
(as an integer, not a string) - Return
{"item_id": 42, "item_type": "int"}
If someone accesses /items/foo
, they'll get a validation error because "foo" can't be converted to an integer:
{
"detail": [
{
"loc": ["path", "item_id"],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
Supported Data Types
FastAPI supports many data types for path parameters, including:
int
: Integer valuesfloat
: Floating-point numbersstr
: Strings (the default)bool
: Boolean valuesUUID
: For UUID stringsEnum
: For predefined sets of values
For example, here's how to use a UUID:
from fastapi import FastAPI
from uuid import UUID
app = FastAPI()
@app.get("/users/{user_id}")
def read_user(user_id: UUID):
return {"user_id": user_id}
This route would match URLs like /users/a86fc66d-4f6a-4b49-9aa7-1b2ba8e6640b
.
Multiple Path Parameters
You can define multiple path parameters in a single route:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_id}/posts/{post_id}")
def get_user_post(user_id: int, post_id: int):
return {"user_id": user_id, "post_id": post_id}
This route handles URLs like /users/42/posts/123
and makes both IDs available to your function.
Predefined Values with Enum
If your path parameter should only accept specific values, you can use Python's Enum
:
from enum import Enum
from fastapi import FastAPI
class ModelName(str, Enum):
alexnet = "alexnet"
resnet = "resnet"
lenet = "lenet"
app = FastAPI()
@app.get("/models/{model_name}")
def get_model(model_name: ModelName):
if model_name == ModelName.alexnet:
return {"model_name": model_name, "message": "Deep Learning FTW!"}
if model_name.value == "lenet":
return {"model_name": model_name, "message": "LeCNN all the images"}
return {"model_name": model_name, "message": "Have some residuals"}
This route will only match /models/alexnet
, /models/resnet
, or /models/lenet
and will reject any other values.
Path Parameters with Paths
Sometimes you need to capture path parameters that contain path characters themselves (like /
). FastAPI supports this with a special path converter:
from fastapi import FastAPI
app = FastAPI()
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"file_path": file_path}
The :path
converter allows the parameter to match any path, including slashes. This route would match URLs like /files/images/logo.png
and /files/documents/report.pdf
.
Order Matters: Path Parameters vs. Fixed Paths
When defining routes with similar patterns, the order matters. For example:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/me")
def read_current_user():
return {"user_id": "the current user"}
@app.get("/users/{user_id}")
def read_user(user_id: str):
return {"user_id": user_id}
If these routes were defined in reverse order, /users/me
would never be matched because FastAPI would think "me" is a user_id
parameter. Always define fixed routes before routes with path parameters.
Real-world Example: Building a Blog API
Let's put everything together in a more comprehensive example by building a simplified blog API:
from fastapi import FastAPI, HTTPException
from enum import Enum
from typing import List, Dict, Any
from pydantic import BaseModel
from uuid import UUID, uuid4
from datetime import datetime
app = FastAPI()
# Mock database
class PostStatus(str, Enum):
draft = "draft"
published = "published"
archived = "archived"
class Post(BaseModel):
id: UUID
title: str
content: str
status: PostStatus
created_at: datetime
# Mock database
posts_db: Dict[UUID, Post] = {}
# Create some sample posts
for i in range(3):
post_id = uuid4()
posts_db[post_id] = Post(
id=post_id,
title=f"Sample Post {i+1}",
content=f"This is sample content for post {i+1}",
status=PostStatus.published,
created_at=datetime.now()
)
# Get all posts
@app.get("/blog/posts")
def get_posts() -> List[Dict[str, Any]]:
return [post.dict() for post in posts_db.values()]
# Get a specific post
@app.get("/blog/posts/{post_id}")
def get_post(post_id: UUID):
if post_id not in posts_db:
raise HTTPException(status_code=404, detail="Post not found")
return posts_db[post_id]
# Filter posts by status
@app.get("/blog/posts/status/{status}")
def get_posts_by_status(status: PostStatus):
filtered_posts = [
post.dict() for post in posts_db.values()
if post.status == status
]
return filtered_posts
# Get posts from a specific year and month
@app.get("/blog/archive/{year}/{month}")
def get_archive_posts(year: int, month: int):
if not (1 <= month <= 12):
raise HTTPException(status_code=400, detail="Month must be between 1 and 12")
filtered_posts = [
post.dict() for post in posts_db.values()
if post.created_at.year == year and post.created_at.month == month
]
return filtered_posts
This example demonstrates:
- Using UUIDs as path parameters for post identification
- Using Enum to restrict post status values
- Multiple path parameters for archive filtering
- Proper error handling with HTTP exceptions
Summary
Path parameters in FastAPI provide a powerful way to create dynamic, RESTful APIs. They allow you to:
- Capture values directly from the URL path
- Automatically convert and validate these values with type annotations
- Use various data types including integers, UUIDs, and enums
- Handle complex paths with multiple parameters
When using path parameters, remember:
- Order matters when defining routes
- Type annotations provide automatic validation
- Path parameters can be combined with query parameters (covered in another tutorial)
Exercises
To solidify your understanding of path parameters, try these exercises:
- Create a route that accepts a username as a path parameter and returns a greeting
- Build a route that accepts two numbers as path parameters and returns their sum
- Implement a product catalog API with routes for categories and products using path parameters
- Create a file management API that uses the
:path
converter to access files in different directories
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)