Service Providers

Service providers are the central place to configure and bootstrap your application. Every major subsystem (database, mail, queue) should be wired through a provider.

The Provider interface

A provider must implement two methods defined in the nimbus package:

type Provider interface {
    Register(app *nimbus.App) error
    Boot(app *nimbus.App) error
}
  • Register — runs first for all providers. Bind services into app.Container. Do not resolve other services here.
  • Boot — runs after every provider has registered. Safe to resolve dependencies and perform setup that relies on other services.

Creating a custom provider

Create a struct that satisfies nimbus.Provider. In Register, bind your service. In Boot, run any initialisation that depends on the container.

package providers

import (
    "github.com/CodeSyncr/nimbus"
    "github.com/CodeSyncr/nimbus/database"
    "github.com/CodeSyncr/nimbus/lucid"
)

type DatabaseProvider struct{}

func (p *DatabaseProvider) Register(app *nimbus.App) error {
    app.Container.Singleton("db", func() (*lucid.DB, error) {
        return database.Connect(config.Database.Driver, config.Database.DSN)
    })
    return nil
}

func (p *DatabaseProvider) Boot(app *nimbus.App) error {
    return nil
}

Registering providers

Register providers in bin/server.go inside the Boot() function, after creating the app:

// bin/server.go
func Boot() *nimbus.App {
    config.Load()
    app := nimbus.New()

    // Register providers in dependency order
    app.Register(&providers.DatabaseProvider{})
    app.Register(&providers.CacheProvider{})
    app.Register(&providers.MailProvider{})

    start.RegisterMiddleware(app)
    start.RegisterRoutes(app)

    return app
}

Provider boot order

app.Boot() (called internally by app.Run()) executes in two passes:

  • Pass 1: Register(app) called on each provider in order. All container bindings are set up.
  • Pass 2: Boot(app) called on each provider in order. Container is fully populated, so any binding can be resolved.

This guarantees that a provider's Boot method can safely call app.Container.Make("db") even if the database provider was registered after it — all registrations happen before any boot.

Accessing the container from providers

Inside Boot, resolve any registered service from the container:

func (p *QueueProvider) Boot(app *nimbus.App) error {
    db := app.Container.MustMake("db").(*gorm.DB)
    q := queue.New(db)
    app.Container.Singleton("queue", func() *queue.Queue {
        return q
    })
    return nil
}

When to use providers

  • Database connections and ORM setup
  • Mail, queue, and cache driver configuration
  • Event listener registration
  • Third-party SDK initialisation
  • Any service that other parts of the app depend on