Container Services

Nimbus provides several built-in packages that can be registered as container services through providers. This page lists the common services and shows how to bind, resolve, and override them.

Database (GORM)

The database package wraps GORM. Bind it as a singleton so the entire app shares one connection pool.

app.Container.Singleton("db", func() (*gorm.DB, error) {
    return database.Connect(app.Config.Database.Driver, app.Config.Database.DSN)
})

// Resolve anywhere:
db := app.Container.MustMake("db").(*gorm.DB)

Logger (Zap)

The logger package exposes a global logger.Log (a *zap.SugaredLogger). You can also bind it into the container for explicit dependency injection.

app.Container.Singleton("logger", func() *zap.SugaredLogger {
    return logger.Log
})

Cache

The cache package provides an in-memory cache store. Register it as a singleton.

app.Container.Singleton("cache", func() *cache.Store {
    return cache.NewStore()
})

// Usage:
store := app.Container.MustMake("cache").(*cache.Store)
store.Set("key", "value", 5*time.Minute)
val, ok := store.Get("key")

Event bus

The events package provides a publish/subscribe event bus.

app.Container.Singleton("events", func() *events.Bus {
    return events.NewBus()
})

bus := app.Container.MustMake("events").(*events.Bus)
bus.On("user.created", sendWelcomeEmail)
bus.Emit("user.created", userData)

Mail

The mail package sends email. Bind the mailer with your SMTP configuration.

app.Container.Singleton("mail", func() *mail.Mailer {
    return mail.New(mail.Config{
        Host: "smtp.example.com",
        Port: 587,
        From: "app@example.com",
    })
})

Queue

The queue package provides background job processing.

app.Container.Singleton("queue", func() *queue.Queue {
    return queue.New()
})

Scheduler

The schedule package runs recurring tasks on a cron-like schedule.

app.Container.Singleton("scheduler", func() *schedule.Scheduler {
    return schedule.New()
})

Registering custom services

Any value can be bound into the container. Use Bind for transient or Singleton for shared instances.

// Transient: new instance every time
app.Container.Bind("pdf", func() *PDFGenerator {
    return NewPDFGenerator()
})

// Singleton: shared across the app
app.Container.Singleton("stripe", func() *StripeClient {
    return NewStripeClient(os.Getenv("STRIPE_KEY"))
})

Overriding defaults

To replace a built-in service, call Bind or Singleton again with the same name. The new constructor replaces the previous one. This is useful for testing or swapping implementations.

// Override "db" with a test database
app.Container.Singleton("db", func() (*gorm.DB, error) {
    return database.Connect("sqlite", ":memory:")
})

Auto-wiring

The container supports automatic constructor parameter resolution. When a binding's constructor function has parameters, the container resolves them by matching their types against registered bindings.

// Register dependencies
app.Container.Singleton("db", func() *gorm.DB {
    db, _ := database.Connect("postgres", dsn)
    return db
})

app.Container.Singleton("cache", func() *cache.Store {
    return cache.NewStore()
})

// OrderService's constructor requires both — auto-resolved:
app.Container.Singleton("orderService", func(db *gorm.DB, store *cache.Store) *OrderService {
    return &OrderService{DB: db, Cache: store}
})

// Works — db and cache are resolved automatically:
svc := app.Container.MustMake("orderService").(*OrderService)

Auto-wiring resolves by exact type match first, then checks interface satisfaction. This eliminates manual MustMake calls inside constructors.