FastAPI boundary
FastAPI Depends adapts HTTP. The app owns service wiring.
FastAPI Depends is excellent at the request boundary. Injex
solves the separate problem of application service wiring reused by
FastAPI, Typer, workers, scripts, and tests.
Use FastAPI Depends for
- request objects, headers, cookies, path/query/body parameters;
- authentication and authorization at the HTTP edge;
- short request-scoped adapters;
- exposing prebuilt services from
app.stateto handlers.
Use plain factories or Injex for
- service graphs reused by FastAPI, Typer, workers, scripts, and tests;
- constructor-injected use cases, repositories, gateways, and clients;
- startup validation of missing registrations or dependency cycles;
- test overrides outside the HTTP layer.
Recommended boundary
def build_services(settings: Settings) -> Services:
container = Container()
container.add_instance(Settings, settings)
container.add_singleton(ApiClient)
container.add_transient(UserRepository)
container.add_transient(RegisterUser)
container.assert_valid()
return Services(container)
FastAPI adapts the graph in lifespan and request dependencies:
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.services = build_services(load_settings())
yield
def get_register_user(request: Request) -> RegisterUser:
return request.app.state.services.register_user
Workers and CLIs use the same builder directly:
services = build_services(load_settings())
services.register_user.execute("ada@example.com")
Rule of thumb: FastAPI adapts HTTP. The application owns service wiring.
Optional integration
If you'd rather not write the lifespan/scope glue by hand, the optional
injex.ext.fastapi integration (install
injex[fastapi]) opens one scope per request, finalizes its
resources when the request ends, and provides a Provide
dependency:
from injex.ext.fastapi import Provide, setup_injex
setup_injex(app, container)
@app.post("/register")
def register_user(email: str, use_case: RegisterUser = Provide(RegisterUser)) -> int:
return use_case.execute(email)
It is a thin adapter over ascope(); the container stays
framework-agnostic, so the same wiring still serves workers, CLIs, and
tests.