Getting Started with godi
Let’s build a real web API in 10 minutes. We’ll create a user service with authentication to show you how godi makes your life easier.
Why Dependency Injection?
The Problem: Your app is growing. Every time you add a dependency, you update 20 files. Testing requires complex setup. Sound familiar?
The Solution: godi wires everything automatically. Change a constructor once, godi handles the rest.
Installation
go get github.com/junioryono/godi/v2
Your First App: User API
Let’s build a real API with users and authentication. We’ll use modules to keep things organized.
Step 1: Define Your Services
Create these files in your project:
services/logger.go
package services
import "log"
type Logger interface {
Info(msg string)
Error(msg string)
}
type ConsoleLogger struct{}
func NewLogger() Logger {
return &ConsoleLogger{}
}
func (l *ConsoleLogger) Info(msg string) {
log.Printf("[INFO] %s", msg)
}
func (l *ConsoleLogger) Error(msg string) {
log.Printf("[ERROR] %s", msg)
}
services/database.go
package services
type Database struct {
logger Logger
}
func NewDatabase(logger Logger) *Database {
logger.Info("Database connected")
return &Database{logger: logger}
}
func (db *Database) Query(sql string) []map[string]interface{} {
db.logger.Info("Executing: " + sql)
// Simulate database query
return []map[string]interface{}{
{"id": 1, "name": "Alice"},
{"id": 2, "name": "Bob"},
}
}
services/user.go
package services
import "fmt"
type UserService struct {
db *Database
logger Logger
}
func NewUserService(db *Database, logger Logger) *UserService {
return &UserService{db: db, logger: logger}
}
func (s *UserService) GetUser(id int) (string, error) {
s.logger.Info(fmt.Sprintf("Getting user %d", id))
users := s.db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %d", id))
if len(users) > 0 {
return users[0]["name"].(string), nil
}
return "", fmt.Errorf("user not found")
}
func (s *UserService) CreateUser(name string) error {
s.logger.Info(fmt.Sprintf("Creating user: %s", name))
// In real app, insert into database
return nil
}
Step 2: Create Modules
This is where godi shines. Create modules/modules.go:
package modules
import (
"github.com/junioryono/godi/v2"
"myapp/services"
)
// CoreModule - Infrastructure services that rarely change
var CoreModule = godi.NewModule("core",
godi.AddSingleton(services.NewLogger),
godi.AddSingleton(services.NewDatabase),
)
// UserModule - All user-related services
var UserModule = godi.NewModule("user",
CoreModule, // Depends on core services
godi.AddScoped(services.NewUserService),
)
// AppModule - Your complete application
var AppModule = godi.NewModule("app",
UserModule,
// Add more modules as your app grows!
)
Step 3: Wire Everything in main.go
package main
import (
"fmt"
"log"
"net/http"
"github.com/junioryono/godi/v2"
"myapp/modules"
"myapp/services"
)
func main() {
// Create DI container
collection := godi.NewServiceCollection()
// Add your app module (includes everything!)
if err := collection.AddModules(modules.AppModule); err != nil {
log.Fatal(err)
}
// Build the provider
provider, err := collection.BuildServiceProvider()
if err != nil {
log.Fatal(err)
}
defer provider.Close()
// Set up HTTP handlers
http.HandleFunc("/users/", handleUser(provider))
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func handleUser(provider godi.ServiceProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Create a scope for this request
scope := provider.CreateScope(r.Context())
defer scope.Close()
// Get services for this request
userService, err := godi.Resolve[*services.UserService](scope)
if err != nil {
http.Error(w, "Service error", http.StatusInternalServerError)
return
}
// Use the service
user, err := userService.GetUser(1)
if err != nil {
http.Error(w, "User not found", http.StatusNotFound)
return
}
fmt.Fprintf(w, "User: %s\n", user)
}
}
Step 4: Run Your App
go run .
# In another terminal:
curl http://localhost:8080/users/
# Output: User: Alice
What Just Happened?
We defined services - Just regular Go types
We created modules - Grouped related services
We used scopes - Each request gets fresh instances
godi wired everything - No manual dependency management!
Adding Features is Easy
Want to add caching? Just update the module:
// services/cache.go
type Cache interface {
Get(key string) (interface{}, bool)
Set(key string, value interface{})
}
type MemoryCache struct {
data map[string]interface{}
}
func NewCache() Cache {
return &MemoryCache{data: make(map[string]interface{})}
}
// Update UserService constructor
func NewUserService(db *Database, logger Logger, cache Cache) *UserService {
return &UserService{db: db, logger: logger, cache: cache}
}
// Update CoreModule
var CoreModule = godi.NewModule("core",
godi.AddSingleton(services.NewLogger),
godi.AddSingleton(services.NewDatabase),
godi.AddSingleton(services.NewCache), // Just add this!
)
That’s it! godi automatically provides the cache to UserService. No need to update main.go or anywhere else.
Testing is a Breeze
Create services/user_test.go:
package services_test
import (
"testing"
"github.com/junioryono/godi/v2"
"myapp/services"
)
// Mock implementations
type MockDatabase struct{}
func (m *MockDatabase) Query(sql string) []map[string]interface{} {
return []map[string]interface{}{
{"id": 1, "name": "Test User"},
}
}
type MockLogger struct{}
func (m *MockLogger) Info(msg string) {}
func (m *MockLogger) Error(msg string) {}
func TestUserService(t *testing.T) {
// Create test module with mocks
testModule := godi.NewModule("test",
godi.AddSingleton(func() services.Logger { return &MockLogger{} }),
godi.AddSingleton(func() *services.Database { return &MockDatabase{} }),
godi.AddScoped(services.NewUserService),
)
// Set up DI
collection := godi.NewServiceCollection()
collection.AddModules(testModule)
provider, _ := collection.BuildServiceProvider()
defer provider.Close()
// Test your service
userService, _ := godi.Resolve[*services.UserService](provider)
user, err := userService.GetUser(1)
if err != nil {
t.Fatal(err)
}
if user != "Test User" {
t.Errorf("Expected Test User, got %s", user)
}
}
Key Concepts
1. Services
Your regular Go types - no magic, no framework dependencies.
2. Modules
Groups of related services. Makes your code organized and reusable.
3. Lifetimes
Singleton: One instance for the entire app (database, logger)
Scoped: New instance per request (user service, repositories)
4. Scopes
Isolate each request. Perfect for web apps!
Next Steps
✅ You’ve learned the basics! Here’s what to explore next:
Web Application Tutorial - Build a complete REST API
Testing Guide - Write better tests with DI
Module Patterns - Advanced module techniques
Tips for Success
Start with modules - Even for small apps, they keep things organized
Use scopes for web apps - Each request should be isolated
Keep constructors simple - Just dependency injection, no logic
Test with mocks - Swap real services for test doubles
Remember: godi is just a tool to wire your dependencies. Your code stays clean, testable, and Go-idiomatic!