FastAPI Integration - InjectQ Documentation
Author: InjectQ Contributors
Description: Modern Python dependency injection library

Skip to content

# FastAPI Integration

FastAPI integration provides seamless dependency injection for FastAPI applications, enabling automatic service resolution with proper request scoping and lifecycle management.

## 🎯 Getting Started

### Basic Setup

    from fastapi import FastAPI
    from injectq import InjectQ
    from injectq.integrations.fastapi import setup_fastapi_integration, InjectQDependency

    # 1. Create container and bind services
    container = InjectQ()
    container.bind(IUserService, UserService())
    container.bind(IOrderService, OrderService())

    # 2. Create FastAPI app
    app = FastAPI(title="My API", version="1.0.0")

    # 3. Set up integration
    setup_fastapi_integration(app, container)

    # 4. Use dependency injection in endpoints
    @app.get("/users/{user_id}")
    async def get_user(
        user_id: int,
        user_service: IUserService = InjectQDependency(IUserService)
    ):
        return user_service.get_user(user_id)

    @app.post("/orders")
    async def create_order(
        order_data: OrderCreate,
        order_service: IOrderService = InjectQDependency(IOrderService)
    ):
        return order_service.create_order(order_data)

### Service Definitions

    from typing import Protocol

    # Define service interfaces
    class IUserService(Protocol):
        def get_user(self, user_id: int) -> User: ...
        def create_user(self, user_data: UserCreate) -> User: ...

    class IOrderService(Protocol):
        def create_order(self, order_data: OrderCreate) -> Order: ...
        def get_order(self, order_id: int) -> Order: ...

    # Implement services
    class UserService:
        def __init__(self, db: IDatabaseConnection):
            self.db = db

        def get_user(self, user_id: int) -> User:
            return self.db.query(User).filter(id=user_id).first()

        def create_user(self, user_data: UserCreate) -> User:
            user = User(**user_data.dict())
            self.db.add(user)
            self.db.commit()
            return user

    class OrderService:
        def __init__(self, db: IDatabaseConnection, user_service: IUserService):
            self.db = db
            self.user_service = user_service

        def create_order(self, order_data: OrderCreate) -> Order:
            # Validate user exists
            user = self.user_service.get_user(order_data.user_id)

            order = Order(**order_data.dict())
            self.db.add(order)
            self.db.commit()
            return order

        def get_order(self, order_id: int) -> Order:
            return self.db.query(Order).filter(id=order_id).first()

## 🔧 Advanced Configuration

### Custom Container Setup

    from injectq import InjectQ, Module

    class ApplicationModule(Module):
        def __init__(self, config: AppConfig):
            self.config = config

        def configure(self, binder):
            # Database
            binder.bind(IDatabaseConnection, create_database_connection(self.config.database))

            # Services
            binder.bind(IUserService, UserService())
            binder.bind(IOrderService, OrderService())

            # External services
            binder.bind(IEmailService, SmtpEmailService(self.config.email))
            binder.bind(IPaymentService, StripePaymentService(self.config.payment))

    def create_app(config: AppConfig) -> FastAPI:
        # Create container with modules
        container = InjectQ()
        container.install(ApplicationModule(config))

        # Create FastAPI app
        app = FastAPI(
            title=config.app_name,
            version=config.version,
            debug=config.debug
        )

        # Set up integration
        setup_fastapi_integration(app, container)

        return app

    # Usage
    config = AppConfig.from_env()
    app = create_app(config)

### Environment-Specific Setup

    def create_container_for_env(env: str) -> InjectQ:
        container = InjectQ()

        if env == "production":
            container.install(ProductionDatabaseModule())
            container.install(RedisCacheModule())
            container.install(SmtpEmailModule())
        elif env == "testing":
            container.install(TestDatabaseModule())
            container.install(InMemoryCacheModule())
            container.install(MockEmailModule())
        else:  # development
            container.install(DevDatabaseModule())
            container.install(InMemoryCacheModule())
            container.install(ConsoleEmailModule())

        return container

    def create_app() -> FastAPI:
        env = os.getenv("ENV", "development")
        container = create_container_for_env(env)

        app = FastAPI()
        setup_fastapi_integration(app, container)

        return app

## 🎨 Dependency Injection Patterns

### Constructor Injection

    # Services with dependencies
    @singleton
    class DatabaseConnection:
        def __init__(self, config: DatabaseConfig):
            self.config = config
            self.connection = create_connection(config)

    @scoped
    class UserService:
        def __init__(self, db: IDatabaseConnection, cache: ICache):
            self.db = db
            self.cache = cache

    # Bind in module
    class ServiceModule(Module):
        def configure(self, binder):
            binder.bind(DatabaseConfig, DatabaseConfig.from_env())
            binder.bind(IDatabaseConnection, DatabaseConnection())
            binder.bind(ICache, RedisCache())
            binder.bind(IUserService, UserService())

    # Use in endpoints
    @app.get("/users/{user_id}")
    async def get_user(
        user_id: int,
        user_service: IUserService = InjectQDependency(IUserService)
    ):
        return user_service.get_user(user_id)

### Request-Scoped Services

    @scoped
    class RequestContext:
        def __init__(self):
            self.request_id = str(uuid.uuid4())
            self.start_time = time.time()
            self.user_id = None

        def set_user(self, user_id: int):
            self.user_id = user_id
            self.request_time = time.time() - self.start_time

    @scoped
    class RequestCache:
        def __init__(self):
            self.data = {}

        def get(self, key: str):
            return self.cache.get(key)

        def set(self, key: str, value):
            self.cache[key] = value

    # Automatic request scoping
    @app.get("/users/{user_id}")
    async def get_user(
        user_id: int,
        ctx: RequestContext = InjectQDependency(RequestContext),
        cache: RequestCache = InjectQDependency(RequestCache),
        user_service: IUserService = InjectQDependency(IUserService)
    ):
        ctx.set_user(user_id)  # Context is scoped to this request

        # Cache is also scoped to this request
        cache_key = f"user:{user_id}"
        user = cache.get(cache_key)

        if user is None:
            user = user_service.get_user(user_id)
            cache.set(cache_key, user)

        return user

### Authentication Integration

    from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
    from fastapi import Depends, HTTPException

    security = HTTPBearer()

    @singleton
    class AuthService:
        def __init__(self, jwt_secret: str):
            self.jwt_secret = jwt_secret

        def verify_token(self, token: str) -> User:
            try:
                payload = jwt.decode(token, self.jwt_secret, algorithms=["HS256"])
                return User(id=payload["user_id"], email=payload["email"])
            except jwt.ExpiredSignatureError:
                raise HTTPException(status_code=401, detail="Token expired")
            except jwt.InvalidTokenError:
                raise HTTPException(status_code=401, detail="Invalid token")

    async def get_current_user(
        credentials: HTTPAuthorizationCredentials = Depends(security),
        auth_service: IAuthService = InjectQDependency(IAuthService)
    ) -> User:
        return auth_service.verify_token(credentials.credentials)

    @app.get("/me")
    async def get_me(current_user: User = Depends(get_current_user)):
        return current_user

    @app.get("/users/{user_id}")
    async def get_user(
        user_id: int,
        current_user: User = Depends(get_current_user),
        user_service: IUserService = InjectQDependency(IUserService)
    ):
        # current_user is authenticated user from JWT
        # user_service is injected from container
        if current_user.id != user_id and not current_user.is_admin:
            raise HTTPException(status_code=403, detail="Not authorized")

        return user_service.get_user(user_id)

## 🧪 Testing FastAPI Integration

### Unit Testing Endpoints

    import pytest
    from fastapi.testclient import TestClient
    from injectq.integrations.fastapi import setup_fastapi_integration

    @pytest.fixture
    def test_app():
        # Create test container
        container = InjectQ()
        container.bind(IUserService, MockUserService())
        container.bind(IOrderService, MockOrderService())

        # Create test app
        app = FastAPI()
        setup_fastapi_integration(app, container)

        @app.get("/users/{user_id}")
        async def get_user(
            user_id: int,
            user_service: IUserService = InjectQDependency(IUserService)
        ):
            return user_service.get_user(user_id)

        return app

    def test_get_user(test_app):
        client = TestClient(test_app)

        response = client.get("/users/123")

        assert response.status_code == 200
        assert response.json()["id"] == 123

    def test_request_scoping(test_app):
        client = TestClient(test_app)

        # Each request should be isolated
        response1 = client.get("/users/1")
        response2 = client.get("/users/2")

        # Both should succeed (no state leakage)
        assert response1.status_code == 200
        assert response2.status_code == 200

### Integration Testing

    @pytest.fixture
    def integration_app():
        # Real container with test database
        container = InjectQ()
        container.install(TestDatabaseModule())
        container.install(UserModule())
        container.install(OrderModule())

        app = FastAPI()
        setup_fastapi_integration(app, container)

        @app.post("/users")
        async def create_user(
            user_data: UserCreate,
            user_service: IUserService = InjectQDependency(IUserService)
        ):
            return user_service.create_user(user_data)

        @app.post("/orders")
        async def create_order(
            order_data: OrderCreate,
            order_service: IOrderService = InjectQDependency(IOrderService)
        ):
            return order_service.create_order(order_data)

        return app

    def test_user_order_workflow(integration_app):
        client = TestClient(integration_app)

        # Create user
        user_response = client.post("/users", json={
            "name": "Test User",
            "email": "test@example.com"
        })
        assert user_response.status_code == 201
        user_id = user_response.json()["id"]

        # Create order for user
        order_response = client.post("/orders", json={
            "user_id": user_id,
            "items": [{"product_id": 1, "quantity": 2}]
        })
        assert order_response.status_code == 201

        order = order_response.json()
        assert order["user_id"] == user_id

### Mock Testing

    class MockUserService:
        def __init__(self):
            self.users = {}
            self.call_count = 0

        def get_user(self, user_id: int):
            self.call_count += 1
            return self.users.get(user_id, {"id": user_id, "name": "Mock User"})

        def create_user(self, user_data):
            user_id = len(self.users) + 1
            user = {"id": user_id, **user_data.dict()}
            self.users[user_id] = user
            return user

    def test_with_mocks():
        container = InjectQ()
        mock_user_service = MockUserService()
        container.bind(IUserService, mock_user_service)

        app = FastAPI()
        setup_fastapi_integration(app, container)

        @app.get("/users/{user_id}")
        async def get_user(
            user_id: int,
            user_service: IUserService = InjectQDependency(IUserService)
        ):
            return user_service.get_user(user_id)

        client = TestClient(app)

        # Test endpoint
        response = client.get("/users/123")
        assert response.status_code == 200

        # Verify mock was called
        assert mock_user_service.call_count == 1

## 🚨 Common Patterns and Pitfalls

### ✅ Good Patterns

#### 1. Proper Scoping

    # ✅ Good: Use scoped for request-specific data
    @scoped
    class RequestMetrics:
        def __init__(self):
            self.start_time = time.time()
            self.queries = []

        def record_query(self, query: str, duration: float):
            self.queries.append({"query": query, "duration": duration})

    # ✅ Good: Use singleton for shared resources
    @singleton
    class DatabasePool:
        def __init__(self, config: DatabaseConfig):
            self.pool = create_pool(config)

    # ✅ Good: Use transient for stateless operations
    @transient
    class PasswordHasher:
        def hash(self, password: str) -> str:
            return bcrypt.hashpw(password.encode(), bcrypt.gensalt())

#### 2. Error Handling

    # ✅ Good: Handle service errors gracefully
    @app.get("/users/{user_id}")
    async def get_user(
        user_id: int,
        user_service: IUserService = InjectQDependency(IUserService)
    ):
        try:
            return user_service.get_user(user_id)
        except UserNotFoundError:
            raise HTTPException(status_code=404, detail="User not found")
        except ServiceError:
            raise HTTPException(status_code=500, detail="Internal server error")

#### 3. Middleware Integration

    # ✅ Good: Use middleware for cross-cutting concerns
    from fastapi import Request

    @app.middleware("http")
    async def logging_middleware(request: Request, call_next):
        start_time = time.time()

        # Get request-scoped logger if available
        try:
            logger = get_request_container(request).get(ILogger)
            logger.info(f"Request started: {request.method} {request.url}")
        except:
            pass  # Logger not available, continue

        response = await call_next(request)

        duration = time.time() - start_time
        print(f"Request completed in {duration:.2f}s")

        return response

### ❌ Bad Patterns

#### 1. Manual Container Access

    # ❌ Bad: Manual container access in endpoints
    container = InjectQ()  # Global container

    @app.get("/users/{user_id}")
    async def get_user(user_id: int):
        user_service = container.get(IUserService)  # Manual resolution
        return user_service.get_user(user_id)

    # ✅ Good: Use dependency injection
    @app.get("/users/{user_id}")
    async def get_user(
        user_id: int,
        user_service: IUserService = InjectQDependency(IUserService)
    ):
        return user_service.get_user(user_id)

#### 2. Singleton Abuse

    # ❌ Bad: Singleton for request-specific data
    @singleton
    class CurrentUser:
        def __init__(self):
            self.user = None

        def set_user(self, user):
            self.user = user  # Shared across requests!

    # ❌ Bad: Singleton for mutable state
    @singleton
    class RequestCache:
        def __init__(self):
            self.data = {}  # Shared and accumulates forever!

    # ✅ Good: Scoped for request-specific data
    @scoped
    class CurrentUser:
        def __init__(self):
            self.user = None

    @scoped
    class RequestCache:
        def __init__(self):
            self.data = {}  # Isolated per request

#### 3. Heavy Services per Request

    # ❌ Bad: Heavy service per request
    @transient
    class MLModelService:
        def __init__(self):
            self.model = load_ml_model()  # 2GB model loaded per request!

    # ✅ Good: Singleton for heavy resources
    @singleton
    class MLModelService:
        def __init__(self):
            self.model = load_ml_model()  # Loaded once

        def predict(self, data):
            return self.model.predict(data)

## ⚡ Advanced Features

### Custom Dependency Resolver

    from injectq.integrations.fastapi import InjectQDependencyResolver

    class CustomResolver(InjectQDependencyResolver):
        def resolve_dependency(self, dependency_type: Type[T]) -> T:
            # Custom resolution logic
            if dependency_type == ISpecialService:
                # Create special service with custom logic
                return SpecialServiceImpl(custom_config)

            # Fall back to container resolution
            return super().resolve_dependency(dependency_type)

    # Use custom resolver
    setup_fastapi_integration(app, container, resolver=CustomResolver())

### Request Container Access

    from injectq.integrations.fastapi import get_request_container

    @app.get("/debug")
    async def debug_endpoint(request: Request):
        # Get the request-scoped container
        request_container = get_request_container(request)

        # Access request-scoped services
        ctx = request_container.get(RequestContext)
        cache = request_container.get(RequestCache)

        return {
            "request_id": ctx.request_id,
            "cache_size": len(cache.data),
            "services": list(request_container._bindings.keys())
        }

### Background Tasks Integration

    from fastapi import BackgroundTasks

    @singleton
    class BackgroundTaskService:
        def __init__(self, email_service: IEmailService):
            self.email_service = email_service

        async def send_welcome_email(self, user_email: str):
            await self.email_service.send_email(
                to=user_email,
                subject="Welcome!",
                body="Welcome to our platform!"
            )

    @app.post("/users")
    async def create_user(
        user_data: UserCreate,
        background_tasks: BackgroundTasks,
        user_service: IUserService = InjectQDependency(IUserService),
        task_service: BackgroundTaskService = InjectQDependency(BackgroundTaskService)
    ):
        # Create user
        user = user_service.create_user(user_data)

        # Send welcome email in background
        background_tasks.add_task(
            task_service.send_welcome_email,
            user.email
        )

        return user

### WebSocket Support

    from fastapi import WebSocket

    @app.websocket("/ws/{client_id}")
    async def websocket_endpoint(
        websocket: WebSocket,
        client_id: str,
        ws_service: IWebSocketService = InjectQDependency(IWebSocketService)
    ):
        await websocket.accept()

        # WebSocket connection gets its own scoped services
        ctx = websocket.scope.get("injectq_container")
        if ctx:
            # Services are scoped to this WebSocket connection
            session_service = ctx.get(ISessionService)
            session_service.set_client_id(client_id)

        while True:
            data = await websocket.receive_text()
            # Handle WebSocket messages with injected services
            response = ws_service.process_message(client_id, data)
            await websocket.send_text(response)

## 🎯 Summary

FastAPI integration provides:

- Automatic dependency injection - No manual container management
- Request-scoped services - Proper isolation per HTTP request
- Type-driven injection - Just add type hints to endpoint parameters
- Framework lifecycle integration - Automatic cleanup and resource management
- Testing support - Easy mocking and test isolation

Key features: - Seamless integration with FastAPI's dependency system - Support for all InjectQ scopes (singleton, scoped, transient) - Request-scoped container access - Custom dependency resolvers - Background task integration - WebSocket support

Best practices: - Use scoped services for request-specific data - Use singleton for shared resources and heavy objects - Use transient for stateless operations - Handle errors gracefully in endpoints - Test thoroughly with mocked dependencies - Avoid manual container access in endpoints

Ready to explore Taskiq integration?