Dependency Injection

Nimbus ships with an IoC (Inversion of Control) container inspired by Laravel conventions. It lives in container.Container and is available as app.Container after nimbus.New().

Binding types

MethodBehaviorUse case
Bind(name, fn)New instance every callRequest-scoped, unique IDs
Singleton(name, fn)Created once, cachedDB connections, SDK clients
Instance(name, value)Pre-built value, no constructorConfig objects, loggers

Transient bindings

app.Container.Bind("requestID", func() string {
    return uuid.New().String()
})

id1, _ := app.Container.Make("requestID")
id2, _ := app.Container.Make("requestID")
// id1 != id2  (new value each time)

Singleton bindings

app.Container.Singleton("db", func() (*nimbus.DB, error) {
    return database.Connect("sqlite", "app.sqlite")
})

db1, _ := app.Container.Make("db")
db2, _ := app.Container.Make("db")
// db1 == db2  (same pointer)

Instance bindings

Register a pre-built value directly without a constructor:

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
app.Container.Instance("logger", logger)

// Resolve later:
l := app.Container.MustMake("logger").(*slog.Logger)

Checking existence

if app.Container.Has("stripe") {
    client := app.Container.MustMake("stripe").(*stripe.Client)
    // use client
}

Resolving bindings

// Safe — returns (value, error)
val, err := app.Container.Make("db")

// Panic on error — use when you're confident the binding exists
db := app.Container.MustMake("db").(*nimbus.DB)

Plugin bindings (HasBindings)

Plugins can declare container bindings via the HasBindings interface. These are registered automatically during the plugin Register phase:

func (p *StripePlugin) Bindings(c *container.Container) {
    c.Singleton("stripe", func() (*stripe.Client, error) {
        return stripe.New(os.Getenv("STRIPE_SECRET_KEY"))
    })
}

// Now any handler can resolve it:
client := app.Container.MustMake("stripe").(*stripe.Client)

Type-safe resolution pattern

Since Make returns any, write thin helpers for type safety:

func ResolveDB(c *container.Container) *nimbus.DB {
    return c.MustMake("db").(*nimbus.DB)
}

// In handler:
db := ResolveDB(app.Container)