godi
Microsoft-style dependency injection for Go with scoped lifetimes and generics
About godi
godi
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, err := services.Build()
if err != nil {
log.Fatal(err)
}
defer provider.Close()
// Scoped services are resolved from a scope (typically one per request)
scope, err := provider.CreateScope(context.Background())
if err != nil {
log.Fatal(err)
}
defer scope.Close()
users := godi.MustResolve[*UserService](scope)
Contents
- Why godi?
- Installation
- Quick Start
- Service Lifetimes
- HTTP Integration
- Features
- Error Handling
- Testing
- Comparison
- Performance
- Documentation
Why godi?
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 lines
The 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 wiring
Installation
go get github.com/junioryono/godi/v6
Requires Go 1.26+. Zero external dependencies.
godi supports the two most recent Go minor releases, like Go itself, and CI tests both. See the Go version policy.
Upgrading from v5? See the v5 → v6 migration guide (summary). v6 is a breaking release at a new import path.
Quick Start
package main
import (
"fmt"
"log"
"github.com/junioryono/godi/v6"
)
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 — registration errors surface here
provider, err := services.Build()
if err != nil {
log.Fatal(err)
}
defer provider.Close()
// 3. Resolve and use - dependencies wired automatically
users := godi.MustResolve[*UserService](provider)
users.Greet() // Hello from UserService!
}
Service Lifetimes
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 resolution
HTTP Integration
godi shines in web applications where each request needs isolated services:
package main
import (
"log"
"net/http"
"github.com/junioryono/godi/v6"
godihttp "github.com/junioryono/godi/http/v6"
)
type Logger struct{}
func NewLogger() *Logger { return &Logger{} }
type UserController struct {
logger *Logger // Injected automatically
}
func NewUserController(logger *Logger) *UserController {
return &UserController{logger: logger}
}
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, err := services.Build()
if err != nil {
log.Fatal(err)
}
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 Support
| Framework | Package | Install |
|---|---|---|
| net/http | github.com/junioryono/godi/http/v6 | go get github.com/junioryono/godi/http/v6 |
| Gin | github.com/junioryono/godi/gin/v6 | go get github.com/junioryono/godi/gin/v6 |
| Chi | github.com/junioryono/godi/chi/v6 | go get github.com/junioryono/godi/chi/v6 |
| Echo v5 | github.com/junioryono/godi/echo/v6 | go get github.com/junioryono/godi/echo/v6 |
| Fiber v3 | github.com/junioryono/godi/fiber/v6 | go get github.com/junioryono/godi/fiber/v6 |
| Huma | github.com/junioryono/godi/huma/v6 | go get github.com/junioryono/godi/huma/v6 |
The trailing /v6 is godi's major version, not the framework's.
Huma runs on top of a router, so pair godi/huma/v6 with the matching router
integration above — the router middleware owns the request scope, and Huma
propagates it to your typed operation handlers.
Features
Interface Binding
Register concrete types as interfaces for easy testing and swapping:
services.AddSingleton(NewConsoleLogger, godi.As[Logger]())
// Resolve by interface
logger := godi.MustResolve[Logger](provider)
Keyed Services
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")
Service Groups
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)
}
Parameter Objects
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}
}
Result Objects
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)
Modules
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,
)
Automatic Cleanup
Services implementing Close() error are cleaned up automatically:
func (d *Database) Close() error {
return d.conn.Close()
}
provider.Close() // Database.Close() called automatically
Error Handling
godi 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 (each is wrapped in a build failed during ... phase: prefix):
- Circular dependencies -
circular dependency detected: *main.A -> *main.B -> *main.A - Missing dependencies -
missing dependencies: *main.UserService requires *main.Database (not registered) [constructor main.NewUserService (user.go:12)] - Lifetime conflicts -
lifetime conflict: *main.Cache (Singleton) cannot depend on *main.RequestContext (Scoped) - Singleton constructor failures - singletons are created during
Build, so their errors surface here too:build failed during singleton-creation phase: failed to initialize singletons: failed to resolve *main.Database: constructor main.NewDatabase (database.go:20) failed: connection refused
Each message is one line. godi.Explain(err) adds the detail: the cycle drawn out, why a lifetime conflict is a problem, and suggested fixes.
Use errors.As with *godi.CircularDependencyError, *godi.MissingDependencyError or *godi.LifetimeConflictError to handle a specific case.
Testing
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
}
Comparison
How godi differs from other Go DI libraries, as of 2026-10 (google/wire v0.7.0, uber-go/fx v1.24.0 on dig v1.19.0, samber/do v2.1.0):
| godi | google/wire | uber-go/fx | samber/do v2 | |
|---|---|---|---|---|
| Approach | Runtime container (reflection, generic resolve helpers) | Compile-time code generation; no runtime container | Runtime container (reflection, built on dig) | Runtime container (generics) |
| Lifetimes | Singleton, scoped, transient | None; the generated injector calls each provider once per injector call | One instance per container; no transient | Lazy or eager singletons, transient |
| Scopes | Scoped services get one instance per scope (e.g. per request), closed with the scope | None | fx.Module/fx.Private and dig scopes control visibility; no per-scope instances or scope disposal | Nested scopes (injector.Scope) control visibility; services registered in a child scope live in it |
| Graph validation | At Build: cycles, missing dependencies, lifetime conflicts; singletons are constructed | At code generation | fx.New runs invokes; fx.ValidateApp checks for missing dependencies without running constructors | None up front; missing services and cycles are reported on invoke |
| Startup / shutdown | Singletons are created at Build; Close() error methods run on scope.Close()/provider.Close() | Providers can return a cleanup func(); the injector returns a combined cleanup | fx.Lifecycle OnStart/OnStop hooks | Shutdown/ShutdownWithContext call Shutdowner implementations (not for transients) |
| Health checks | No | No | No | Yes (HealthCheck, Healthchecker) |
| Names and groups | godi.Name, godi.Group, godi.In/godi.Out tags | No; use distinct types | Yes (name/group tags, fx.ResultTags) | Named services; no groups |
| HTTP integration | Scope middleware for net/http, Chi, Echo, Fiber, Gin; Huma handlers | No | No | No |
| Status | Active | Archived 2025-08-25 (goforj/wire is a maintained fork) | Maintained | Maintained |
Sources: wire README and guide; fx package docs and lifecycle; dig package docs; do docs on transients, health checks and shutdown. Corrections are welcome.
Performance
The repository includes package-level benchmarks and a separate comparison suite for dig and do. Dependency versions are locked in benchmarks/go.mod, and every run records the commit, timestamp, Go version, OS, and architecture alongside the raw results.
make benchmark
Benchmark results depend on the machine, toolchain, and system load. A nightly CI
workflow publishes the raw benchmark-results artifact and compares it with the
previous run using benchstat;
compare repeated samples instead of treating a single run as a stable product claim.
The libraries do not expose identical operations (dig, for example, has no
resolve-by-type API), so the comparison source
documents what each benchmark measures.
Documentation
- Getting Started - 5-minute tutorial
- Core Concepts - Lifetimes, scopes, modules
- Features - Keyed services, groups, parameter objects
- Integrations - Gin, Chi, Echo v5, Fiber v3, net/http, Huma
- Guides - Web apps, testing, error handling
- API Reference
- Executable Quick Start
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
MIT License - see LICENSE for details.
Frequently Asked Questions
What is godi?
godi is a Miscellaneous library for the Go programming language. Microsoft-style dependency injection for Go with scoped lifetimes and generics
How do I install godi?
Install godi with the Go module system using `go get junioryono/godi`. Check the repository for the current installation instructions.
What category does godi belong to?
godi is listed under Miscellaneous, specifically Dependency Injection.