LogoRevali

Request-Scoped Dependencies

One instance per request, disposed when the request ends

registerRequestScoped builds a dependency once per request and shares it for the rest of that request. Nothing is shared between requests.

That is the missing middle between the two registrations you already have:

RegistrationLifetimeUse for
registerSingleton / registerLazySingleton The whole process Connection pools, config, clients
registerRequestScoped One request Transactions, units of work, the current user
registerFactoryEvery resolutionCheap, stateless helpers

Registering#

@App()
final class MyApp extends AppConfig {
  const MyApp() : super(host: 'localhost', port: 8080);

  @override
  Future<void> configureDependencies(DI di) async {
    di
      ..registerSingleton<Database>(Database(pool))
      ..registerRequestScoped<UnitOfWork>(() => UnitOfWork(di.get<Database>()));
  }
}

Resolving#

Inject it the same way as anything else — in handlers, middleware, guards, interceptors and exception catchers:

@Controller('orders')
class OrdersController {
  const OrdersController();

  @Post()
  Future<Order> create(@Body() OrderBody body, @Dep() UnitOfWork work) async {
    return work.orders.insert(body);
  }
}

Everything within one request gets the same instance, so a middleware that records the caller and a handler that reads it are talking about the same object — without threading it through by hand.

It is built lazily. A request that never asks for it never constructs one.

Releasing what it holds#

Implement Disposable and the framework releases it when the request ends:

class UnitOfWork implements Disposable {
  UnitOfWork(this._db) : _transaction = _db.begin();

  final Database _db;
  final Transaction _transaction;

  @override
  Future<void> dispose() async => _transaction.commit();
}

Disposal runs whether the request succeeded or threw, in reverse creation order — so a dependency can rely on whatever it was built from still being alive while it shuts down. It also runs after the response has been fully written, which matters for streaming and SSE handlers that are still using their resources as they send.

Resolving outside a request#

There is no ambient request to scope to, so it throws:

Bad state: UnitOfWork is registered as request scoped and cannot be resolved
outside a request.

Building one anyway would hand back an instance nothing disposes, shared with nobody — exactly the bug request scoping exists to prevent. If you need it during startup, it wants to be a singleton or a factory instead.

What's next?#