Claude Skills Guide

Claude Code FastAPI Dependency Injection Patterns Guide

FastAPI’s dependency injection system is one of its most powerful features, enabling clean architecture and testable code. When combined with Claude Code, you can rapidly implement robust dependency injection patterns that follow best practices. This guide walks you through practical examples and actionable patterns you can apply immediately to your Python APIs.

Understanding FastAPI Dependency Injection

FastAPI’s dependency injection system allows you to declare dependencies at the route level, and FastAPI automatically resolves them for each request. This pattern promotes loose coupling, improves testability, and makes your code more maintainable.

The foundation of FastAPI’s DI system is the Depends() function. It lets you define dependencies that FastAPI will execute before your route handler, automatically injecting the results into your function parameters.

Basic Dependency Injection Patterns

Simple Dependency with Depends

The most straightforward pattern is creating a reusable dependency function that returns a value used by your route handler:

from fastapi import FastAPI, Depends

app = FastAPI()

def get_database_url():
    return "postgresql://localhost/mydb"

@app.get("/items")
async def read_items(db_url: str = Depends(get_database_url)):
    return {"database": db_url}

This pattern separates configuration from your route logic. When testing, you can easily override get_database_url with a test database.

Class-Based Dependencies

For more complex dependencies, use classes. FastAPI will instantiate them and handle async properly:

from fastapi import Depends

class DatabaseConnection:
    def __init__(self, config: dict):
        self.config = config
        self.connection = None
    
    async def connect(self):
        # Establish connection
        self.connection = await self.config["pool"].acquire()
        return self.connection
    
    async def disconnect(self):
        if self.connection:
            await self.config["pool"].release(self.connection)

def get_db(config: dict) -> DatabaseConnection:
    return DatabaseConnection(config)

@app.post("/users")
async def create_user(
    db: DatabaseConnection = Depends(get_db),
    user_data: dict = Body(...)
):
    conn = await db.connect()
    # Create user logic

Claude Code can help you scaffold these patterns quickly by understanding your project structure and generating appropriate class hierarchies.

Practical Dependency Patterns for Real Applications

Authentication Dependencies

One of the most common use cases is implementing authentication:

from fastapi import HTTPException, Security
from fastapi.security import HTTPBearer

security = HTTPBearer()

async def verify_token(token: str = Security(security)):
    if not token:
        raise HTTPException(status_code=403, detail="Missing token")
    
    # Validate token (JWT, API key, etc.)
    payload = await validate_jwt(token)
    if not payload:
        raise HTTPException(status_code=401, detail="Invalid token")
    
    return payload

@app.get("/protected-resource")
async def get_protected(
    user: dict = Depends(verify_token)
):
    return {"user": user["sub"], "data": "sensitive info"}

Optional Dependencies with Default Values

Sometimes you need dependencies that are optional:

from typing import Optional

async def get_cache():
    # Return cache client or None
    return CacheClient() if CACHE_ENABLED else None

@app.get("/items")
async def get_items(
    cache: Optional[CacheClient] = Depends(get_cache)
):
    if cache:
        cached = await cache.get("items")
        if cached:
            return cached
    
    items = await fetch_items_from_db()
    
    if cache:
        await cache.set("items", items, ttl=300)
    
    return items

Parameterized Dependencies

For dependencies that need configuration, use factories:

def create_rate_limiter(max_requests: int, window: int):
    async def rate_limiter(request: Request):
        client_id = request.client.host
        key = f"rate:{client_id}"
        
        count = await redis.get(key)
        if count and int(count) >= max_requests:
            raise HTTPException(429, "Too many requests")
        
        await redis.incr(key)
        await redis.expire(key, window)
        
        return True
    
    return rate_limiter

@app.post("/api/data")
async def create_data(
    rate_limit: bool = Depends(create_rate_limiter(100, 60))
):
    # Your endpoint logic

Advanced Patterns for Production Systems

DependencyOverrides for Testing

FastAPI provides a powerful testing mechanism through dependency overrides:

from fastapi.testclient import TestClient
from fastapi import Depends

# In your test file
def get_mock_db():
    return MockDatabase()

app.dependency_overrides[get_database_url] = get_mock_db

client = TestClient(app)
response = client.get("/items")
assert response.status_code == 200

Nested Dependencies

Dependencies can depend on other dependencies, creating a clean hierarchy:

async def get_current_user(token: str = Depends(verify_token)):
    return await fetch_user(token)

async def get_user_profile(user: dict = Depends(get_current_user)):
    profile = await fetch_profile(user["id"])
    return {"user": user, "profile": profile}

@app.get("/profile")
async def read_profile(
    data: dict = Depends(get_user_profile)
):
    return data

This pattern keeps each dependency focused on a single responsibility while composing complex behaviors.

Conditional Dependencies

Use dependency conditions for feature flags or environment-specific behavior:

def get_storage_backend():
    if os.getenv("STORAGE") == "s3":
        return S3Storage()
    return LocalStorage()

@app.post("/upload")
async def upload_file(
    storage = Depends(get_storage_backend)
):
    # Upload using the appropriate backend

Actionable Best Practices

  1. Keep dependencies focused: Each dependency should do one thing well. Don’t bundle multiple responsibilities in a single dependency.

  2. Use async by default: Define your dependencies with async def unless they perform synchronous blocking operations.

  3. Leverage type hints: FastAPI uses type hints to validate and convert data. Always include proper types.

  4. Document dependencies: Add docstrings explaining what each dependency does and any side effects.

  5. Test with overrides: Use FastAPI’s dependency_overrides for comprehensive testing without mocking internals.

  6. Handle cleanup properly: If your dependency allocates resources, consider using context managers or generator patterns for proper cleanup.

Conclusion

FastAPI’s dependency injection system provides a robust foundation for building scalable Python APIs. By using these patterns with Claude Code’s assistance, you can rapidly implement clean, testable, and maintainable code structures. Start with simple dependencies and gradually adopt more advanced patterns as your application grows in complexity.

Remember that the key to successful dependency injection is maintaining clear boundaries between components while keeping your code flexible for testing and future modifications.

Built by theluckystrike — More at zovo.one