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 resolveapp.bind<PostRepository>((c) => PostRepository(c.make<Database>()));
// Built once, reused foreverapp.singleton<Database>((c) => Database(config('database.url')));
// An object you already haveapp.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 thingapp<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 boundWhat 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, notapp. A bareappresolves to the globalapp<T>()helper rather than the inherited field — Dart looks through the library scope before it looks at inherited members — and you get the confusing errorThe 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.