Skip to content

Service Container

The container holds your application’s services and hands them out on demand. Application is a container, so anywhere you have the application you have the container.

The common pattern is to bind an application’s abstraction once and resolve it where the request flow begins:

Binding

Bindings are keyed by type. There is no autowiring — Dart has no reflection at runtime, and Maat uses none — so you tell the container how to build a thing exactly once:

// A new instance on every resolve
app.bind<PostRepository>((c) => PostRepository(c.make<Database>()));
// Built once, reused forever
app.singleton<Database>((c) => Database(config('database.url')));
// An object you already have
app.instance<Clock>(SystemClock());

The factory receives the container, so a service may resolve its own dependencies.

Resolving

final repo = app.make<PostRepository>();
final repo = app<PostRepository>(); // the global helper, same thing

app<T>() resolves from the current application, which is what makes services reachable from a route handler without threading the container through every call.

Route.get('/posts', (Request request) => app<PostRepository>().all());

Ask for something unbound and you get a BindingResolutionException naming the type and telling you how to bind it — not a null.

app.has<PostRepository>(); // true when bound

What the Framework Binds

Application.create() binds these before your providers run, so you may resolve them anywhere:

Type Description
Application The application itself.
Config The configuration repository.
Env The loaded environment.
Router The route collection.
Dispatcher The event dispatcher behind Event and event().
MiddlewareConfig The global stack and aliases.
ExceptionHandler The error renderer.
HttpKernel The request dispatcher.

Packages add their own bindings through service providers. The standard application skeleton includes:

Type Provider Description
MailManager MailServiceProvider Cached named mailers from config('mail').
ChannelManager NotificationServiceProvider Mail, database, and custom notification channels.

Where to Bind

Bindings belong in a service provider’s register method. See Service Providers.

class AppServiceProvider extends ServiceProvider {
AppServiceProvider(super.app);
@override
void register() {
this.app.singleton<PostRepository>((c) => PostRepository());
}
}

Inside a provider, write this.app, not app. A bare app resolves to the global app<T>() helper rather than the inherited field — Dart looks through the library scope before it looks at inherited members — and you get the confusing error The method 'singleton' isn't defined for the type 'Function'. This mirrors Laravel, where you also write $this->app->singleton(...).

Replacing a Binding in Tests

Register a fake instance after the application is created. The new instance replaces the existing binding for that type:

final fake = FakePostRepository();
application.instance<PostRepository>(fake);
expect(application.make<PostRepository>(), same(fake));

This keeps route and controller tests focused without adding a mocking layer to the container.