Awesome Go

godi

CategoryMiscellaneous
SubcategoryDependency Injection
Stars79

Microsoft-style dependency injection for Go with scoped lifetimes and generics

About godi

godi

Go Reference Go Report Card Build Status Coverage License: MIT

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?

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 │            │
│  └──────────────┘  └──────────────┘  └──────────────┘            │
└──────────────────────────────────────────────────────────────────┘
LifetimeCreatedSharedUse Case
SingletonOnceApp-wideDatabase pools, config
ScopedOnce per scopeWithin scopeRequest context, transactions
TransientEvery timeNeverBuilders, 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

FrameworkPackageInstall
net/httpgithub.com/junioryono/godi/http/v6go get github.com/junioryono/godi/http/v6
Gingithub.com/junioryono/godi/gin/v6go get github.com/junioryono/godi/gin/v6
Chigithub.com/junioryono/godi/chi/v6go get github.com/junioryono/godi/chi/v6
Echo v5github.com/junioryono/godi/echo/v6go get github.com/junioryono/godi/echo/v6
Fiber v3github.com/junioryono/godi/fiber/v6go get github.com/junioryono/godi/fiber/v6
Humagithub.com/junioryono/godi/huma/v6go 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):

godigoogle/wireuber-go/fxsamber/do v2
ApproachRuntime container (reflection, generic resolve helpers)Compile-time code generation; no runtime containerRuntime container (reflection, built on dig)Runtime container (generics)
LifetimesSingleton, scoped, transientNone; the generated injector calls each provider once per injector callOne instance per container; no transientLazy or eager singletons, transient
ScopesScoped services get one instance per scope (e.g. per request), closed with the scopeNonefx.Module/fx.Private and dig scopes control visibility; no per-scope instances or scope disposalNested scopes (injector.Scope) control visibility; services registered in a child scope live in it
Graph validationAt Build: cycles, missing dependencies, lifetime conflicts; singletons are constructedAt code generationfx.New runs invokes; fx.ValidateApp checks for missing dependencies without running constructorsNone up front; missing services and cycles are reported on invoke
Startup / shutdownSingletons are created at Build; Close() error methods run on scope.Close()/provider.Close()Providers can return a cleanup func(); the injector returns a combined cleanupfx.Lifecycle OnStart/OnStop hooksShutdown/ShutdownWithContext call Shutdowner implementations (not for transients)
Health checksNoNoNoYes (HealthCheck, Healthchecker)
Names and groupsgodi.Name, godi.Group, godi.In/godi.Out tagsNo; use distinct typesYes (name/group tags, fx.ResultTags)Named services; no groups
HTTP integrationScope middleware for net/http, Chi, Echo, Fiber, Gin; Huma handlersNoNoNo
StatusActiveArchived 2025-08-25 (goforj/wire is a maintained fork)MaintainedMaintained

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

Full Documentation

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.

← Back to Dependency Injection