Skip to main content

FastAPI Path Dependencies

Introduction

Path dependencies are a powerful feature in FastAPI that allow you to create reusable components that run before your route handlers. They can validate input data, extract information from requests, or perform any necessary preprocessing. By attaching these dependencies to specific paths, you can ensure that certain operations happen every time a particular endpoint is accessed, leading to cleaner, more maintainable code.

In this tutorial, we'll explore how path dependencies work in FastAPI, how to create and use them, and how they can improve your API development workflow.

Understanding Path Dependencies

Path dependencies in FastAPI are functions that are executed before the route handler function. They can:

  • Validate request parameters
  • Extract information from the request
  • Perform authentication and authorization
  • Share common code across multiple endpoints
  • Return data that can be used by the route handler

Let's dive into how these dependencies work.

Basic Path Dependency Example

Here's a simple example of a path dependency in action:

python
from fastapi import FastAPI, Depends, HTTPException

app = FastAPI()

async def verify_token(token: str):
if token != "mysecrettoken":
raise HTTPException(status_code=401, detail="Invalid token")
return token

@app.get("/items/")
async def read_items(token: str = Depends(verify_token)):
return {"token": token, "message": "Token is valid"}

In this example, the verify_token function is a dependency that checks whether the provided token is valid. The route handler only executes if the token passes verification.

Example request:

bash
curl -X GET "http://localhost:8000/items/?token=mysecrettoken"

Response:

json
{
"token": "mysecrettoken",
"message": "Token is valid"
}

If an invalid token is provided:

bash
curl -X GET "http://localhost:8000/items/?token=wrongtoken"

Response:

json
{
"detail": "Invalid token"
}

Path Dependencies vs Query Parameters

While our first example used a query parameter, path dependencies can also interact with path parameters, which is where they get their name:

python
from fastapi import FastAPI, Depends, HTTPException, Path

app = FastAPI()

async def verify_item_exists(item_id: int = Path(...)):
valid_items = {1: "Hammer", 2: "Screwdriver", 3: "Wrench"}
if item_id not in valid_items:
raise HTTPException(status_code=404, detail="Item not found")
return {"item_id": item_id, "item_name": valid_items[item_id]}

@app.get("/items/{item_id}")
async def read_item(item: dict = Depends(verify_item_exists)):
return item

In this example, the dependency receives the path parameter directly and verifies that the item exists before the route handler runs.

Creating Reusable Dependencies

One of the main benefits of dependencies is reusability. You can define a dependency once and use it in multiple routes:

python
from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

async def get_current_user(authorization: str = Header(None)):
if not authorization:
raise HTTPException(status_code=401, detail="Authorization header missing")
if authorization != "Bearer mysecrettoken":
raise HTTPException(status_code=401, detail="Invalid authentication token")
return {"username": "johndoe"}

@app.get("/users/me")
async def read_user_me(current_user: dict = Depends(get_current_user)):
return current_user

@app.get("/items/")
async def read_items(current_user: dict = Depends(get_current_user)):
return {"user": current_user, "items": ["Item1", "Item2"]}

Now both endpoints reuse the same authentication logic.

Dependencies with Dependencies

FastAPI allows you to create dependencies that themselves depend on other dependencies, creating a chain:

python
from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

async def verify_token(authorization: str = Header(None)):
if not authorization:
raise HTTPException(status_code=401, detail="Authorization header missing")
if authorization != "Bearer mysecrettoken":
raise HTTPException(status_code=401, detail="Invalid authentication token")
return authorization

async def get_current_user(token: str = Depends(verify_token)):
# In a real app, you'd decode the token and fetch the user
return {"username": "johndoe", "email": "[email protected]"}

@app.get("/users/me")
async def read_user_me(current_user: dict = Depends(get_current_user)):
return current_user

In this example, get_current_user depends on verify_token, creating a chain of dependencies.

Path Operation Decorators with Dependencies

You can also add dependencies to a path operation decorator, which is useful when you want the dependency to run but don't need to use its return value:

python
from fastapi import FastAPI, Depends, HTTPException, Header

app = FastAPI()

async def verify_admin(authorization: str = Header(None)):
if not authorization:
raise HTTPException(status_code=401, detail="Authorization header missing")
if authorization != "Bearer admin-token":
raise HTTPException(status_code=403, detail="Admin access required")
return True

@app.get("/admin/", dependencies=[Depends(verify_admin)])
async def admin_portal():
return {"message": "Welcome to the admin portal"}

With this approach, the verify_admin dependency will run when the endpoint is accessed, but its return value is ignored.

Practical Example: API Rate Limiting

Let's implement a simple rate limiting system using dependencies:

python
from fastapi import FastAPI, Depends, HTTPException, Request
import time
from collections import defaultdict

app = FastAPI()

# Simple in-memory rate limit store
rate_limiter = defaultdict(list)
RATE_LIMIT = 5 # requests per minute

async def check_rate_limit(request: Request):
client_ip = request.client.host
now = time.time()

# Remove requests older than 60 seconds
rate_limiter[client_ip] = [t for t in rate_limiter[client_ip] if now - t < 60]

# Check if rate limit exceeded
if len(rate_limiter[client_ip]) >= RATE_LIMIT:
raise HTTPException(
status_code=429,
detail="Rate limit exceeded. Try again in a minute."
)

# Record this request
rate_limiter[client_ip].append(now)

return True

@app.get("/limited-resource/", dependencies=[Depends(check_rate_limit)])
async def limited_resource():
return {"message": "This is a rate-limited resource"}

This example implements a basic rate limiter that allows each IP address to make only 5 requests per minute to the /limited-resource/ endpoint.

Router-level Dependencies

You can also apply dependencies to an entire router, making them run for all routes in that router:

python
from fastapi import FastAPI, Depends, HTTPException, APIRouter

app = FastAPI()

async def verify_api_key(api_key: str = None):
if not api_key or api_key != "valid_key":
raise HTTPException(status_code=403, detail="Invalid API key")
return api_key

# Create a router with dependencies
api_router = APIRouter(dependencies=[Depends(verify_api_key)])

@api_router.get("/items/")
async def read_items():
return {"items": ["Item1", "Item2"]}

@api_router.get("/users/")
async def read_users():
return {"users": ["User1", "User2"]}

# Include the router in the app
app.include_router(api_router, prefix="/api")

Now both /api/items/ and /api/users/ will require a valid API key.

Using Classes as Dependencies

For more complex dependencies, you can use classes with the __call__ method:

python
from fastapi import FastAPI, Depends

app = FastAPI()

class DatabaseConnection:
def __init__(self):
# In a real app, you'd initialize your database connection here
self.db_connected = True

def __call__(self):
if not self.db_connected:
raise Exception("Database not connected")
# In a real app, you'd return a database session here
return {"status": "connected"}

db_dependency = DatabaseConnection()

@app.get("/db-status/")
async def get_db_status(db: dict = Depends(db_dependency)):
return db

This approach is particularly useful when your dependency needs to be configured or has its own internal state.

Summary

Path dependencies in FastAPI are a powerful tool for:

  • Code reuse across multiple endpoints
  • Validating input data and parameters
  • Authentication and authorization
  • Preprocessing requests before they reach your route handlers
  • Implementing cross-cutting concerns like logging or rate limiting

By leveraging dependencies effectively, you can write cleaner, more modular FastAPI applications that are easier to maintain and extend.

Additional Resources

Exercises

  1. Create a path dependency that logs all requests to an endpoint, including the method, path, and timestamp.
  2. Implement a dependency that validates that a user has permission to access a specific resource based on a user ID and resource ID.
  3. Create a caching dependency that stores responses for GET requests and returns the cached response if the same request is made within 60 seconds.

By mastering path dependencies in FastAPI, you'll be able to create more robust, maintainable APIs with cleaner code organization and better separation of concerns.



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