DI Container
Kruda includes a built-in dependency injection container. Services are registered on a *Container and resolved in handlers via *Ctx.
Creating a Container
c := kruda.NewContainer()
app := kruda.New(kruda.WithContainer(c))Or let modules create it automatically:
app := kruda.New()
app.Module(&MyModule{}) // creates container if neededRegistering Services
Give — Singleton
func (c *Container) Give(instance any) errorRegisters a pre-built singleton instance:
c := kruda.NewContainer()
c.Give(&UserService{db: connectDB()})GiveLazy — Lazy Singleton
func (c *Container) GiveLazy(factory any) errorRegisters a factory that runs once on first resolution:
c.GiveLazy(func() (*DBPool, error) {
return sql.Open("postgres", os.Getenv("DATABASE_URL"))
})GiveTransient — New Instance Per Resolution
func (c *Container) GiveTransient(factory any) errorRegisters a factory that creates a new instance every time:
c.GiveTransient(func() (*RequestLogger, error) {
return &RequestLogger{}, nil
})GiveNamed — Named Instance
func (c *Container) GiveNamed(name string, instance any) errorRegisters a named instance:
c.GiveNamed("primary", &DB{DSN: "primary-dsn"})
c.GiveNamed("replica", &DB{DSN: "replica-dsn"})GiveAs — Interface Registration
func (c *Container) GiveAs(instance any, ifacePtr any) errorRegisters an instance under an interface type:
c.GiveAs(&PostgresRepo{}, (*UserRepository)(nil))Resolving in Handlers
Use package-level generic functions with *Ctx:
Resolve
func Resolve[T any](c *Ctx) (T, error)app.Get("/users", func(c *kruda.Ctx) error {
svc, err := kruda.Resolve[*UserService](c)
if err != nil {
return err
}
return c.JSON(svc.ListAll())
})MustResolve
func MustResolve[T any](c *Ctx) TLike Resolve but panics on error (caught by Recovery middleware):
app.Get("/users", func(c *kruda.Ctx) error {
svc := kruda.MustResolve[*UserService](c)
return c.JSON(svc.ListAll())
})ResolveNamed / MustResolveNamed
func ResolveNamed[T any](c *Ctx, name string) (T, error)
func MustResolveNamed[T any](c *Ctx, name string) Tprimary := kruda.MustResolveNamed[*DB](c, "primary")
replica := kruda.MustResolveNamed[*DB](c, "replica")Low-Level Container Resolution
For use outside of handlers (e.g., in tests or setup):
func Use[T any](c *Container) (T, error)
func UseNamed[T any](c *Container, name string) (T, error)Modules
Group related service registrations into modules:
type Module interface {
Install(c *Container) error
}type UserModule struct{}
func (m *UserModule) Install(c *kruda.Container) error {
c.Give(&UserRepository{})
c.Give(&UserService{})
return nil
}
app.Module(&UserModule{})Lifecycle
Services implementing Initializer or Shutdowner are managed automatically:
type Initializer interface {
Init(ctx context.Context) error
}
type Shutdowner interface {
Shutdown(ctx context.Context) error
}func (db *DBPool) Init(ctx context.Context) error {
return db.Ping(ctx)
}
func (db *DBPool) Shutdown(ctx context.Context) error {
return db.Close()
}InjectMiddleware
func (c *Container) InjectMiddleware() HandlerFuncReturns middleware that makes the container available to handlers for Resolve/MustResolve:
app.Use(container.InjectMiddleware())This is set up automatically when using WithContainer(c).
Example
package main
import "github.com/go-kruda/kruda"
type GreetService struct {
prefix string
}
func (s *GreetService) Greet(name string) string {
return s.prefix + " " + name
}
func main() {
c := kruda.NewContainer()
c.Give(&GreetService{prefix: "Hello"})
app := kruda.New(kruda.WithContainer(c))
app.Get("/greet/:name", func(c *kruda.Ctx) error {
svc := kruda.MustResolve[*GreetService](c)
msg := svc.Greet(c.Param("name"))
return c.JSON(map[string]string{"message": msg})
})
app.Listen(":3000")
}