Skip to content

Custom Providers

Nika's DI container provides several ways to register and customize providers beyond simple constructor functions.

Standard Provider (Constructor Function)

The most common way to register a provider is through a constructor function. Nika resolves all dependencies automatically:

func NewUserService(repo *UserRepository, cache *cache.Cache) *UserService {
    return &UserService{
        repo:  repo,
        cache: cache,
    }
}

func (m *UsersModule) Providers() []interface{} {
    return []interface{}{
        NewUserService, // Nika resolves *UserRepository and *cache.Cache
    }
}

Instance Provider

You can provide a pre-constructed instance directly:

func (m *ConfigModule) Providers() []interface{} {
    return []interface{}{
        &ConfigService{
            DBHost:     "localhost",
            DBPort:     5432,
            CacheTTL:   300,
        },
    }
}

RegisterSingleton

Use app.RegisterSingleton() to register a singleton directly in the App container, before modules are loaded:

func main() {
    app := nika.NewApp()

    // Register singletons before loading modules
    db, _ := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    app.RegisterSingleton(db)

    app.LoadModule(rootModule)
    app.Listen(":3000")
}

Interface-based Providers

When a constructor returns an interface, Nika registers both the concrete type and the interface:

type CacheProvider interface {
    Get(ctx context.Context, key string) (string, error)
    Set(ctx context.Context, key string, value any, ttl time.Duration) error
}

func NewRedisCacheProvider(url string) CacheProvider {
    return &redisProvider{client: redis.NewClient(...)}
}

// Nika registers the instance under:
// - *redisProvider (concrete type)
// - CacheProvider (interface return type)

Provider with Setup Pattern

Common packages use the Setup() pattern, which internally calls RegisterSingleton:

func Setup(app *nika.App, cfg Config) (*Cache, error) {
    provider, err := NewRedisProvider(cfg.URL)
    if err != nil {
        return nil, err
    }
    cache := &Cache{Provider: provider}
    app.RegisterSingleton(cache)
    return cache, nil
}

Dependency Resolution Chain

Nika resolves dependencies in the order modules are loaded:

LoadModule(AppModule)
  ├── LoadModule(ConfigModule)
  │     └── Register: *Config
  ├── LoadModule(DatabaseModule)
  │     └── Register: *MongoDB, *mongo.Database
  ├── LoadModule(UsersModule)
  │     ├── Register: *UserRepository (needs *mongo.Database ✓)
  │     └── Register: *UserService (needs *UserRepository ✓)
  └── Register Controllers
        └── UserController (needs *UserService ✓)

Summary

Method Auto-resolve When to Use
Constructor function Most providers with dependencies
Direct instance Simple config or constant values
RegisterSingleton External resources, pre-built instances
Setup() pattern Common package integration