Skip to content

Service Providers

View raw .md

Service providers are the central bootstrapping point for Magic applications: each provider's register method binds services into the IoC container, and boot runs async setup after all providers have registered.

On this page

Introduction#

Service providers are the central place of all Magic application bootstrapping. Your own application, as well as all of Magic's core services, are bootstrapped via service providers.

But, what do we mean by "bootstrapped"? In general, we mean registering things, including registering service container bindings, event listeners, middleware, and even routes. Service providers are the central place to configure your application.

If you open the config/app.dart file included with Magic, you will see a providers array. These are all of the service provider classes that will be loaded for your application.

Generating Providers#

Use the Magic CLI to generate a new service provider class:

bash
dart run magic:artisan make:provider Payment

This creates lib/app/providers/payment_service_provider.dart with a ServiceProvider stub containing empty register() and boot() methods.

Writing Service Providers#

All service providers extend the ServiceProvider class. Most service providers contain a register and a boot method. Within the register method, you should only bind things into the service container. Within the boot method, you may do anything else—register routes, event listeners, or any other functionality.

dart
import 'package:magic/magic.dart';

class PaymentServiceProvider extends ServiceProvider {
  PaymentServiceProvider(super.app);

  @override
  void register() {
    // Bind services into the container
    app.singleton('payment.gateway', () => StripeGateway());
    app.bind('payment.processor', () => PaymentProcessor());
  }

  @override
  Future<void> boot() async {
    // Called after all providers are registered
    final gateway = app.make<StripeGateway>('payment.gateway');
    await gateway.initialize();
  }
}

The Register Method#

Within the register method, you should only bind things into the service container. You should never attempt to register any event listeners, routes, or any other piece of functionality within the register method.

This is because you may accidentally use a service from another provider that has not been loaded yet.

dart
@override
void register() {
  // ✅ Good: Only bind services
  app.singleton('analytics', () => AnalyticsService());
  app.bind('report', () => ReportGenerator());
  
  // ❌ Bad: Don't use other services here
  // final auth = Auth.instance; // May not be ready!
}

Binding Types

The service container supports two types of bindings:

Method Description
app.bind(key, closure) Creates a new instance each time
app.singleton(key, closure) Creates a single shared instance
dart
// New instance every time
app.bind('report', () => ReportGenerator());

// Same instance every time (singleton)
app.singleton('database', () => DatabaseConnection());

The Boot Method#

The boot method is called after all other service providers have been registered, meaning you have access to all services that have been registered by the framework:

dart
@override
Future<void> boot() async {
  // ✅ Safe to access any registered service
  final config = Config.get('payment');
  final auth = Auth.instance;

  // Event listeners belong in register(), not here: the auth guard can
  // dispatch AuthLogin during AuthServiceProvider.boot, before this runs.

  // Perform async initialization
  await initializePaymentGateway();
}

Registering Providers#

All service providers are registered in your config/app.dart configuration file using the providers key. Each provider is a factory function that receives the application instance:

dart
import 'package:magic/magic.dart';

Map<String, dynamic> get appConfig => {
  'app': {
    'name': 'My App',
    'providers': [
      // Framework Providers
      (app) => CacheServiceProvider(app),
      (app) => NetworkServiceProvider(app),
      (app) => AuthServiceProvider(app),
      
      // Application Providers
      (app) => AppServiceProvider(app),
      (app) => PaymentServiceProvider(app),
    ],
  },
};

Provider Loading Order#

Providers are loaded in the order they are listed:

  1. Register Phase: register() is called on each provider in order
  2. Boot Phase: boot() is called on each provider in order

Deferred Providers#

If your provider is only registering bindings in the service container without any boot logic, you can make it simpler by returning early from boot:

dart
class SimpleServiceProvider extends ServiceProvider {
  SimpleServiceProvider(super.app);

  @override
  void register() {
    app.singleton('simple', () => SimpleService());
  }

  @override
  Future<void> boot() async {
    // No boot logic needed
  }
}

Built-in Providers#

Magic includes several built-in service providers for core functionality:

Provider Purpose
CacheServiceProvider Cache stores and drivers
NetworkServiceProvider HTTP client configuration
AuthServiceProvider Authentication guards and state
LocalizationServiceProvider Language and locale support
VaultServiceProvider Secure storage
DatabaseServiceProvider Database connections
EventServiceProvider Event dispatching
GateServiceProvider Authorization gates and policies

These providers are typically registered in your config/app.dart to enable framework features.

esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.