FastAPI Middleware and Dependency Injection: Patterns for Clean Code Architecture

FastAPI Middleware and Dependency Injection: Patterns for Clean Code Architecture

FastAPI is a modern, fast (high-performance) web framework for building APIs with Python. Its design encourages clean code architecture through features like middleware and dependency injection. These patterns help developers create scalable, maintainable, and testable applications.

Understanding Middleware in FastAPI

Middleware in FastAPI allows you to process requests and responses globally. It acts as a layer that can modify, intercept, or log data passing through your application. Middleware is useful for tasks such as authentication, logging, CORS handling, and more.

Implementing Middleware

To add middleware in FastAPI, you use the add_middleware method. FastAPI supports Starlette middleware, enabling integration with various middleware classes.

Example:

from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware

app = FastAPI()

class CustomMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        # Pre-processing logic
        response = await call_next(request)
        # Post-processing logic
        return response

app.add_middleware(CustomMiddleware)

Dependency Injection in FastAPI

FastAPI’s dependency injection system allows you to define reusable components that can be injected into path operations, functions, or other dependencies. This promotes loose coupling and enhances testability.

Creating Dependencies

Dependencies are functions or classes that provide specific functionality or data. They are declared using the Depends class.

Example:

from fastapi import Depends, FastAPI

app = FastAPI()

def get_query_token():
    return "secret-token"

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

Patterns for Combining Middleware and Dependency Injection

Using middleware and dependency injection together enhances application architecture. Middleware can handle cross-cutting concerns, while dependencies manage specific logic or data retrieval.

Example: Authentication

Implement middleware for global authentication checks, and dependencies for route-specific authorization logic.

Middleware example:

from starlette.middleware.base import BaseHTTPMiddleware

class AuthMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        token = request.headers.get("Authorization")
        if token != "expected_token":
            return JSONResponse(status_code=401, content={"detail": "Unauthorized"})
        return await call_next(request)

app.add_middleware(AuthMiddleware)

Dependency example:

from fastapi import Depends, HTTPException

def verify_user(token: str = Depends(get_query_token)):
    if token != "secret-token":
        raise HTTPException(status_code=403, detail="Forbidden")
    return True

@app.get("/secure-data/")
async def get_secure_data(authorized: bool = Depends(verify_user)):
    return {"data": "This is secure"}

Best Practices for Clean Architecture

  • Separate concerns: Use middleware for cross-cutting concerns and dependencies for business logic.
  • Keep dependencies lightweight and focused on a single responsibility.
  • Inject dependencies explicitly for better testability.
  • Use class-based dependencies for complex logic or stateful components.
  • Leverage FastAPI’s dependency overrides for testing.

Conclusion

FastAPI’s middleware and dependency injection features provide powerful patterns for building clean, scalable, and maintainable APIs. Combining these tools allows developers to implement robust security, logging, and business logic with clear separation of concerns.