Table of Contents
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.