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.