Skip to main content

FastAPI Dependencies Introduction

When building web applications, we often need to reuse code for common operations like:

  • Authenticating users
  • Enforcing permissions
  • Database connections
  • Parameter validation
  • Common processing logic

FastAPI's dependency injection system provides an elegant way to handle these scenarios, making your code more maintainable, testable, and cleaner.

What Are Dependencies in FastAPI?

Dependencies in FastAPI are functions (or callable objects) that can be "injected" into your path operation functions. When a request is made, FastAPI will:

  1. Identify the required dependencies
  2. Execute them in the right order
  3. Provide their results to your endpoint function

Think of dependencies as building blocks that handle specific tasks before your main endpoint logic runs.

Basic Dependency Example

Let's start with a simple example:

python
from fastapi import FastAPI, Depends

app = FastAPI()

# This is our dependency
def get_query_parameter(q: str = None):
return q or "No query parameter provided"

@app.get("/items/")
async def read_items(query_result: str = Depends(get_query_parameter)):
return {"query_result": query_result}

In this example:

  • get_query_parameter is a dependency function
  • We inject it into our path operation using Depends()
  • The return value from get_query_parameter is passed to read_items as query_result

If you visit /items/?q=test, you'll see:

json
{
"query_result": "test"
}

If you visit /items/ without a query parameter:

json
{
"query_result": "No query parameter provided"
}

How Dependency Injection Works

When someone makes a request to your endpoint, FastAPI:

  1. Calls the dependency function (e.g., get_query_parameter)
  2. Gets the result
  3. Assigns that result to the parameter in your path operation function (e.g., query_result)
  4. Then executes your endpoint function with that parameter

Types of Dependencies

1. Simple Dependencies

As we've already seen, simple dependencies are regular functions:

python
def get_db():
db = DBSession()
try:
yield db # This returns the database connection
finally:
db.close() # This ensures the connection is closed after the endpoint is processed

2. Class-based Dependencies

You can also use classes with a __call__ method:

python
class QueryChecker:
def __call__(self, q: str = None):
if q:
return q
return "No query provided"

query_checker = QueryChecker()

@app.get("/check/")
async def check_query(result: str = Depends(query_checker)):
return {"result": result}

3. Dependencies with Sub-dependencies

Dependencies can themselves depend on other dependencies:

python
def get_query(q: str = None):
return q or "default"

def process_query(query: str = Depends(get_query)):
return f"processed: {query}"

@app.get("/process/")
async def process_endpoint(final_result: str = Depends(process_query)):
return {"result": final_result}

Practical Example: Authentication System

Let's create a simple authentication dependency:

python
from fastapi import FastAPI, Depends, HTTPException, Header
from typing import Optional

app = FastAPI()

# Our "database" of API keys
API_KEYS = {
"valid_key123": "user1",
"another_key456": "user2"
}

def get_current_user(api_key: Optional[str] = Header(None)):
if api_key is None:
raise HTTPException(status_code=401, detail="API Key header is missing")

if api_key not in API_KEYS:
raise HTTPException(status_code=403, detail="Invalid API Key")

return {"username": API_KEYS[api_key]}

@app.get("/protected/")
async def protected_route(current_user: dict = Depends(get_current_user)):
return {
"message": "You accessed a protected route",
"user": current_user["username"]
}

In this example:

  1. get_current_user is our dependency function
  2. It checks if an API key is present and valid
  3. If valid, it returns user information
  4. If invalid or missing, it raises an HTTP exception
  5. Our endpoint receives the user information when authentication succeeds

Benefits of Dependencies

  1. Code Reuse: Write once, use everywhere
  2. Separation of Concerns: Isolate authentication, validation, etc.
  3. Testability: Dependencies can be easily mocked for testing
  4. Maintainability: Centralized logic is easier to update
  5. Readability: Endpoint functions stay focused on their primary logic

Common Use Cases

  • Database connections
  • Authentication and authorization
  • Logging
  • Request validation
  • Rate limiting
  • Feature flags
  • Caching mechanisms

Dependency Performance Considerations

When multiple endpoints use the same dependency, FastAPI optimizes performance by caching certain dependencies. This is controlled through the use_cache parameter:

python
@app.get("/no-cache/")
async def no_cache_endpoint(fresh_data: dict = Depends(get_data, use_cache=False)):
return fresh_data

Summary

FastAPI's dependency injection system is a powerful tool that helps you:

  • Create modular, reusable components
  • Keep your code DRY (Don't Repeat Yourself)
  • Separate concerns in your application
  • Write cleaner path operation functions

By understanding dependencies, you're taking a significant step toward writing maintainable and professional APIs with FastAPI.

Exercises

  1. Create a dependency that validates a user's age from a query parameter and ensures it's at least 18
  2. Implement a dependency that logs all requests to an endpoint, including request method and path
  3. Create a chain of dependencies that: validates a token, fetches a user from a database, and checks if the user has admin privileges

Additional Resources



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