godi

June 13, 2026 · View on GitHub

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)
}

user := godi.MustResolve[*UserService](provider)

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/v5

Requires Go 1.26+. Zero external dependencies.

Upgrading from v4? See the v4 → v5 migration guide — v5 is a breaking release at a new import path.

Quick Start

package main

import (
    "fmt"
    "log"

    "github.com/junioryono/godi/v5"
)

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/v5"
    godihttp "github.com/junioryono/godi/http/v5"
)

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, 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/v5go get github.com/junioryono/godi/http/v5
Gingithub.com/junioryono/godi/gin/v5go get github.com/junioryono/godi/gin/v5
Chigithub.com/junioryono/godi/chi/v5go get github.com/junioryono/godi/chi/v5
Echogithub.com/junioryono/godi/echo/v5go get github.com/junioryono/godi/echo/v5
Fibergithub.com/junioryono/godi/fiber/v5go get github.com/junioryono/godi/fiber/v5
Humagithub.com/junioryono/godi/huma/v5go get github.com/junioryono/godi/huma/v5

Huma runs on top of a router, so pair godi/v5/huma 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:

  • Circular dependencies - *A -> *B -> *A
  • Missing dependencies - *UserService requires *Database (not registered)
  • Lifetime conflicts - singleton *Cache cannot depend on scoped *RequestContext

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

FeaturegodiWireFxdo
No code generationYesNoYesYes
Service lifetimesYesNoNoNo
Scoped servicesYesNoNoNo
Build-time validationYesYesNoNo
HTTP framework integrationYesNoNoNo
Parameter objectsYesNoYesNo
Automatic cleanupYesNoYesYes

Performance

Benchmarks comparing godi with dig (Uber's DI, powers Fx) and do (samber's DI).

Run on Apple M2 Max (Go 1.26). 🏆 marks the fastest in each benchmark. Source code.

Singleton Resolution

Libraryns/opB/opallocs/opvs fastest
godi 🏆5500fastest
do18019263.3x slower
dig6407362012x slower

godi uses a lock-free cache with zero allocations. Singletons are created at build time, so every resolution is a fast cache lookup.

Concurrent Resolution

Libraryns/opB/opallocs/opvs fastest
godi 🏆700fastest
do297224642x slower
dig3667362052x slower

Under high concurrency, godi is 40-50x faster with zero contention.

Transient Resolution

Libraryns/opB/opallocs/opvs fastest
godi 🏆176402fastest
do18820871.1x slower

New instance created on each call.

Cold Start

Libraryns/opB/opallocs/opvs fastest
godi 🏆17,50021,064155fastest
dig36,00037,6655492.1x slower
do47,00030,5113512.7x slower

Full cycle: create container, register services, build, resolve. godi is 2x faster than dig.

Run benchmarks yourself
cd benchmarks && go test -bench=. -benchmem

Documentation

Full Documentation

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE for details.