Dependency injection for Go with service lifetimes. godi automatically wires your application, manages service lifetimes, and handles cleanup - so you can focus on business logic.
services := godi.NewCollection() services.AddSingleton(NewDatabase) // One instance, shared everywhere services.AddScoped(NewUserService) // One instance per request services.AddTransient(NewEmailBuilder) // New instance every time provider, _ := services.Build() user := godi.MustResolve[*UserService](provider)The problem: As applications grow, manually wiring dependencies becomes painful. Constructor parameters multiply, initialization order matters, and per-request isolation requires careful scope management.
// Manual wiring - gets messy fast config := NewConfig() logger := NewLogger(config) db := NewDatabase(config, logger) cache := NewCache(config, logger) userRepo := NewUserRepository(db, cache, logger) orderRepo := NewOrderRepository(db, cache, logger) userService := NewUserService(userRepo, logger) orderService := NewOrderService(orderRepo, userService, logger) // ... 20 more linesThe solution: Register constructors. godi figures out the rest.
// godi - register in any order, resolve anything services := godi.NewCollection() services.AddSingleton(NewConfig) services.AddSingleton(NewLogger) services.AddSingleton(NewDatabase) services.AddScoped(NewUserService) // ... godi handles the wiringgo get github.com/junioryono/godi/v4Requires Go 1.21+. Zero external dependencies.
package main import ( "fmt" "github.com/junioryono/godi/v4" ) type Logger struct{} func (l *Logger) Log(msg string) { fmt.Println(msg) } func NewLogger() *Logger { return &Logger{} } type UserService struct { logger *Logger } func NewUserService(logger *Logger) *UserService { return &UserService{logger: logger} } func (s *UserService) Greet() { s.logger.Log("Hello from UserService!") } func main() { // 1. Register services services := godi.NewCollection() services.AddSingleton(NewLogger) services.AddSingleton(NewUserService) // 2. Build the container provider, _ := services.Build() defer provider.Close() // 3. Resolve and use - dependencies wired automatically users := godi.MustResolve[*UserService](provider) users.Greet() // Hello from UserService! }godi provides three lifetimes to control when instances are created:
┌──────────────────────────────────────────────────────────────────┐ │ Application │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ SINGLETON: Database, Logger, Config │ │ │ │ Created once, shared everywhere │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Request 1 │ │ Request 2 │ │ Request 3 │ │ │ │ SCOPED: │ │ SCOPED: │ │ SCOPED: │ │ │ │ Transaction │ │ Transaction │ │ Transaction │ │ │ │ UserSession │ │ UserSession │ │ UserSession │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └──────────────────────────────────────────────────────────────────┘ | Lifetime | Created | Shared | Use Case |
|---|---|---|---|
Singleton | Once | App-wide | Database pools, config |
Scoped | Once per scope | Within scope | Request context, transactions |
Transient | Every time | Never | Builders, temp objects |
services.AddSingleton(NewDatabasePool) // One pool for the whole app services.AddScoped(NewTransaction) // Fresh transaction per request services.AddTransient(NewQueryBuilder) // New builder every resolutiongodi shines in web applications where each request needs isolated services:
package main import ( "net/http" "github.com/junioryono/godi/v4" godihttp "github.com/junioryono/godi/v4/http" ) type UserController struct { // Dependencies injected automatically } func (c *UserController) List(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`["alice", "bob"]`)) } func main() { services := godi.NewCollection() services.AddSingleton(NewLogger) services.AddScoped(NewUserController) provider, _ := services.Build() defer provider.Close() mux := http.NewServeMux() mux.HandleFunc("GET /users", godihttp.Handle((*UserController).List)) // ScopeMiddleware creates a fresh scope per request handler := godihttp.ScopeMiddleware(provider)(mux) http.ListenAndServe(":8080", handler) }| Framework | Package | Install |
|---|---|---|
| net/http | github.com/junioryono/godi/v4/http | go get github.com/junioryono/godi/v4/http |
| Gin | github.com/junioryono/godi/v4/gin | go get github.com/junioryono/godi/v4/gin |
| Chi | github.com/junioryono/godi/v4/chi | go get github.com/junioryono/godi/v4/chi |
| Echo | github.com/junioryono/godi/v4/echo | go get github.com/junioryono/godi/v4/echo |
| Fiber | github.com/junioryono/godi/v4/fiber | go get github.com/junioryono/godi/v4/fiber |
Register concrete types as interfaces for easy testing and swapping:
services.AddSingleton(NewConsoleLogger, godi.As[Logger]()) // Resolve by interface logger := godi.MustResolve[Logger](provider)Multiple implementations of the same type:
services.AddSingleton(NewPrimaryDB, godi.Name("primary")) services.AddSingleton(NewReplicaDB, godi.Name("replica")) primary := godi.MustResolveKeyed[Database](provider, "primary") replica := godi.MustResolveKeyed[Database](provider, "replica")Collect related services for batch operations:
services.AddSingleton(NewEmailValidator, godi.Group("validators")) services.AddSingleton(NewPhoneValidator, godi.Group("validators")) validators := godi.MustResolveGroup[Validator](provider, "validators") for _, v := range validators { v.Validate(input) }Simplify constructors with many dependencies:
type ServiceParams struct { godi.In DB Database Cache Cache Logger Logger Metrics Metrics `optional:"true"` } func NewService(params ServiceParams) *Service { return &Service{db: params.DB, cache: params.Cache} }Register multiple services from one constructor:
type InfraResult struct { godi.Out DB *Database Cache *Cache Health *HealthChecker } func NewInfra(cfg *Config) InfraResult { db := connectDB(cfg) return InfraResult{ DB: db, Cache: NewCache(cfg), Health: NewHealthChecker(db), } } // One registration, three services services.AddSingleton(NewInfra)Organize large applications:
// users/module.go var Module = godi.NewModule("users", godi.AddScoped(NewUserRepository), godi.AddScoped(NewUserService), ) // main.go services.AddModules( infrastructure.Module, users.Module, orders.Module, )Services implementing Close() error are cleaned up automatically:
func (d *Database) Close() error { return d.conn.Close() } provider.Close() // Database.Close() called automaticallygodi validates at build time to catch problems early:
provider, err := services.Build() if err != nil { // Circular dependency? Missing service? Lifetime conflict? // The error message tells you exactly what's wrong log.Fatal(err) }Common errors caught at build time:
- Circular dependencies -
*A -> *B -> *A - Missing dependencies -
*UserService requires *Database (not registered) - Lifetime conflicts -
singleton *Cache cannot depend on scoped *RequestContext
Replace implementations for testing:
func TestUserService(t *testing.T) { services := godi.NewCollection() services.AddSingleton(func() Database { return &MockDB{} }) services.AddScoped(NewUserService) provider, _ := services.Build() defer provider.Close() scope, _ := provider.CreateScope(context.Background()) defer scope.Close() svc := godi.MustResolve[*UserService](scope) // Test with mock database }| Feature | godi | Wire | Fx | do |
|---|---|---|---|---|
| No code generation | Yes | No | Yes | Yes |
| Service lifetimes | Yes | No | No | No |
| Scoped services | Yes | No | No | No |
| Build-time validation | Yes | Yes | No | No |
| HTTP framework integration | Yes | No | No | No |
| Parameter objects | Yes | No | Yes | No |
| Automatic cleanup | Yes | No | Yes | Yes |
Benchmarks comparing godi with dig (Uber's DI, powers Fx) and do (samber's DI).
Run on Apple M2 Max. Source code.
| Library | ns/op | B/op | allocs/op | vs godi |
|---|---|---|---|---|
| godi | 55 | 0 | 0 | 1x |
| do | 180 | 192 | 6 | 3.3x |
| dig | 640 | 736 | 20 | 12x |
godi uses a lock-free cache with zero allocations. Singletons are created at build time, so every resolution is a fast cache lookup.
| Library | ns/op | B/op | allocs/op | vs godi |
|---|---|---|---|---|
| godi | 7 | 0 | 0 | 1x |
| do | 297 | 224 | 6 | 42x |
| dig | 366 | 736 | 20 | 52x |
Under high concurrency, godi is 40-50x faster with zero contention.
| Library | ns/op | B/op | allocs/op |
|---|---|---|---|
| godi | 176 | 40 | 2 |
| do | 188 | 208 | 7 |
New instance created on each call.
| Library | ns/op | B/op | allocs/op | vs godi |
|---|---|---|---|---|
| godi | 17,500 | 21,064 | 155 | 1x |
| dig | 36,000 | 37,665 | 549 | 2.1x |
| do | 47,000 | 30,511 | 351 | 2.7x |
Full cycle: create container, register services, build, resolve. godi is 2x faster than dig.
Run benchmarks yourself
cd benchmarks && go test -bench=. -benchmem- Getting Started - 5-minute tutorial
- Core Concepts - Lifetimes, scopes, modules
- Features - Keyed services, groups, parameter objects
- Integrations - Gin, Chi, Echo, Fiber, net/http
- Guides - Web apps, testing, error handling
- API Reference
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.