Skip to content

FastAPI Integration

This guide shows how to integrate dijay with FastAPI.

Helpers

Create two helper functions that bridge FastAPI's Depends with dijay's container:

from typing import Annotated, Any

from fastapi import Depends, Request


def inject[T](token: type[T]) -> T:
    """Resolve a dependency from the dijay container."""
    async def use(request: Request) -> T:
        return await request.app.state.container.resolve(token, id=str(id(request)))

    return Annotated[token, Depends(use)]


def guard(token: type, *args) -> Any:
    """Resolve a guard and execute it with the current request."""
    async def use(request: Request):
        instance = await request.app.state.container.resolve(token, id=str(id(request)))
        return await instance(request, *args)

    return Annotated[Any, Depends(use)]
  • inject(Token) resolves and returns the instance of the token.
  • guard(Token, *args) resolves the instance, calls it with the request and extra arguments, and returns the result.

Each request gets a unique id based on the request object, enabling REQUEST-scoped dependencies.

Server as Injectable

Encapsulate the FastAPI app inside an injectable class so dijay manages its lifecycle:

from fastapi import FastAPI

from dijay import injectable


@injectable()
class HttpServer:
    def __init__(self, config: HttpConfig):
        self.config = config
        self.app = FastAPI(title="My API", version="0.0.1")
        # register routes, middleware, etc.

The container stores the HttpServer as a singleton. Its dependencies (like HttpConfig) are resolved automatically.

Running the Application

Use the container as an async context manager and resolve the server to run it:

import asyncio

import uvicorn

from dijay import Container

from .module import AppModule
from .presentation.http.server import HttpServer


async def main():
    async with Container.from_module(AppModule) as container:
        server = await container.resolve(HttpServer)
        server.app.state.container = container
        config = uvicorn.Config(
            server.app,
            host=server.config.host,
            port=server.config.port,
        )
        await uvicorn.Server(config).serve()

The async with block ensures that @on_bootstrap hooks run before serving and @on_shutdown hooks run on exit.

For development with auto-reload, use watchfiles instead of uvicorn's --reload:

from watchfiles import run_process

from .presentation.http.config import HttpConfig

config = HttpConfig()

if config.enable_reload:
    run_process("./src", target=lambda: asyncio.run(main()))
else:
    asyncio.run(main())

This re-runs the entire main() on file changes, so the container is always rebuilt cleanly.

Using Routes

Use inject to resolve dependencies in route parameters:

@app.get("/users")
async def list_users(service: inject(UserService)):
    return await service.get_all()

@app.post("/users")
async def create_user(service: inject(UserService), body: CreateUserDTO):
    return await service.create(body)

Guards

A guard is an injectable class with a __call__ method that validates the request:

import jwt
from fastapi import HTTPException, Request

from dijay import injectable


@injectable()
class PermissionGuard:
    def __init__(self, user_service: UserService):
        self.user_service = user_service

    async def __call__(self, request: Request, roles: list[str]):
        token = (request.headers.get("authorization") or "").removeprefix("Bearer ").strip()
        if not token:
            raise HTTPException(401)

        try:
            payload = jwt.decode(token, "SECRET", algorithms=["HS256"])
        except jwt.InvalidTokenError:
            raise HTTPException(401)

        user = await self.user_service.find_by_id(payload["sub"])
        if not user or not any(r in user.roles for r in roles):
            raise HTTPException(403, "Insufficient permissions")

        return user

Use the guard helper to resolve and execute it in a single step:

@app.get("/me")
async def me(user: guard(PermissionGuard, ["user", "admin"])):
    return {"id": user.id, "email": user.email}

Chaining Guards

Guards can be chained so each one handles a single responsibility.

By parameter order — each guard communicates via request.state:

@injectable()
class FooGuard:
    async def __call__(self, request: Request):
        request.state.foo = "foo_value"
        return request.state.foo


@injectable()
class BarGuard:
    async def __call__(self, request: Request):
        foo = request.state.foo  # set by FooGuard
        return f"{foo}_bar"


@app.get("/example")
async def example(foo: guard(FooGuard), bar: guard(BarGuard)):
    return {"foo": foo, "bar": bar}

The execution order follows the parameter declaration: FooGuard runs first, then BarGuard.

By dependency chain — a guard declares another guard as a dependency in its __call__:

@injectable()
class FooGuard:
    async def __call__(self, request: Request):
        return "foo_value"


@injectable()
class BarGuard:
    async def __call__(self, request: Request, foo: guard(FooGuard)):
        return f"{foo}_bar"


@app.get("/example")
async def example(bar: guard(BarGuard)):
    return {"bar": bar}

BarGuard explicitly depends on FooGuard, so the execution order is guaranteed regardless of parameter position. Only the final guard needs to appear in the route.

Full Example

See the fastapi-ddd recipe for a complete example with:

  • DDD-style architecture (domain, application, infrastructure, presentation)
  • Abstract ports with concrete adapters via @injectable(Token)
  • Custom OpenAPI configuration
  • Development and production entry points