Chi Integration

Complete guide for using godi with the Chi router.

Installation

go get github.com/junioryono/godi/v4
go get github.com/junioryono/godi/v4/chi

Quick Start

package main

import (
    "encoding/json"
    "net/http"

    "github.com/go-chi/chi/v5"
    "github.com/junioryono/godi/v4"
    godichi "github.com/junioryono/godi/v4/chi"
)

type UserController struct{}

func NewUserController() *UserController {
    return &UserController{}
}

func (c *UserController) List(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode([]string{"alice", "bob"})
}

func main() {
    services := godi.NewCollection()
    services.AddScoped(NewUserController)

    provider, _ := services.Build()
    defer provider.Close()

    r := chi.NewRouter()
    r.Use(godichi.ScopeMiddleware(provider))
    r.Get("/users", godichi.Handle((*UserController).List))

    http.ListenAndServe(":8080", r)
}

ScopeMiddleware

Creates a request scope for each HTTP request.

r := chi.NewRouter()
r.Use(godichi.ScopeMiddleware(provider))

Configuration Options

r.Use(godichi.ScopeMiddleware(provider,
    // Custom error handler for scope creation failures
    godichi.WithErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
        http.Error(w, "Service unavailable", http.StatusServiceUnavailable)
    }),

    // Custom handler for scope close errors
    godichi.WithCloseErrorHandler(func(err error) {
        log.Printf("Scope close error: %v", err)
    }),

    // Middleware that runs after scope creation
    godichi.WithMiddleware(func(scope godi.Scope, r *http.Request) error {
        reqCtx := godi.MustResolve[*RequestContext](scope)
        reqCtx.UserID = r.Header.Get("X-User-ID")
        return nil
    }),
))

Handle

Wraps a controller method for type-safe resolution.

type UserController interface {
    List(http.ResponseWriter, *http.Request)
    GetByID(http.ResponseWriter, *http.Request)
    Create(http.ResponseWriter, *http.Request)
}

r.Get("/users", godichi.Handle(UserController.List))
r.Get("/users/{id}", godichi.Handle(UserController.GetByID))
r.Post("/users", godichi.Handle(UserController.Create))

Handler Options

r.Get("/users", godichi.Handle(UserController.List,
    // Enable panic recovery
    godichi.WithPanicRecovery(true),

    // Custom panic handler
    godichi.WithPanicHandler(func(w http.ResponseWriter, r *http.Request, v any) {
        log.Printf("Panic: %v", v)
        http.Error(w, "Unexpected error", http.StatusInternalServerError)
    }),

    // Custom scope error handler
    godichi.WithScopeErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
        http.Error(w, "Session error", http.StatusInternalServerError)
    }),

    // Custom resolution error handler
    godichi.WithResolutionErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
        http.Error(w, "Service unavailable", http.StatusServiceUnavailable)
    }),
))

Complete Example

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/go-chi/chi/v5/middleware"
    "github.com/google/uuid"
    "github.com/junioryono/godi/v4"
    godichi "github.com/junioryono/godi/v4/chi"
)

// === Services ===

type Logger struct{}

func NewLogger() *Logger { return &Logger{} }

func (l *Logger) Info(msg string, args ...any) {
    log.Printf("[INFO] "+msg, args...)
}

type RequestContext struct {
    ID        string
    UserID    string
    StartTime time.Time
}

func NewRequestContext() *RequestContext {
    return &RequestContext{
        ID:        uuid.New().String()[:8],
        StartTime: time.Now(),
    }
}

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

type UserService struct {
    reqCtx *RequestContext
    logger *Logger
}

func NewUserService(reqCtx *RequestContext, logger *Logger) *UserService {
    return &UserService{reqCtx: reqCtx, logger: logger}
}

func (s *UserService) GetAll() []User {
    s.logger.Info("[%s] Fetching all users", s.reqCtx.ID)
    return []User{{ID: 1, Name: "Alice"}, {ID: 2, Name: "Bob"}}
}

// === Controllers ===

type UserController struct {
    service *UserService
    reqCtx  *RequestContext
}

func NewUserController(service *UserService, reqCtx *RequestContext) *UserController {
    return &UserController{service: service, reqCtx: reqCtx}
}

func (c *UserController) List(w http.ResponseWriter, r *http.Request) {
    users := c.service.GetAll()
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("X-Request-ID", c.reqCtx.ID)
    json.NewEncoder(w).Encode(map[string]any{
        "users":    users,
        "duration": time.Since(c.reqCtx.StartTime).String(),
    })
}

func (c *UserController) GetByID(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("X-Request-ID", c.reqCtx.ID)
    json.NewEncoder(w).Encode(User{ID: 1, Name: "User " + id})
}

// === Main ===

func main() {
    // Register services
    services := godi.NewCollection()
    services.AddSingleton(NewLogger)
    services.AddScoped(NewRequestContext)
    services.AddScoped(NewUserService)
    services.AddScoped(NewUserController)

    // Build provider
    provider, err := services.Build()
    if err != nil {
        log.Fatalf("Failed to build provider: %v", err)
    }
    defer provider.Close()

    // Create router
    r := chi.NewRouter()

    // Chi middleware
    r.Use(middleware.Logger)
    r.Use(middleware.Recoverer)

    // godi scope middleware
    r.Use(godichi.ScopeMiddleware(provider,
        godichi.WithMiddleware(func(scope godi.Scope, req *http.Request) error {
            reqCtx := godi.MustResolve[*RequestContext](scope)
            reqCtx.UserID = req.Header.Get("X-User-ID")
            return nil
        }),
    ))

    // Routes
    r.Get("/users", godichi.Handle((*UserController).List))
    r.Get("/users/{id}", godichi.Handle((*UserController).GetByID))

    // Health check
    r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("OK"))
    })

    // Start server
    log.Println("Server starting on :8080")
    http.ListenAndServe(":8080", r)
}

Route Groups

Use with Chi route groups:

r.Route("/api/v1", func(r chi.Router) {
    r.Route("/users", func(r chi.Router) {
        r.Get("/", godichi.Handle((*UserController).List))
        r.Get("/{id}", godichi.Handle((*UserController).GetByID))
        r.Post("/", godichi.Handle((*UserController).Create))
    })

    r.Route("/orders", func(r chi.Router) {
        r.Get("/", godichi.Handle((*OrderController).List))
        r.Post("/", godichi.Handle((*OrderController).Create))
    })
})

Accessing URL Parameters

Use Chi’s URL parameter functions in your controllers:

func (c *UserController) GetByID(w http.ResponseWriter, r *http.Request) {
    id := chi.URLParam(r, "id")
    // ...
}

Accessing Scope Manually

r.Get("/custom", func(w http.ResponseWriter, r *http.Request) {
    scope, err := godi.FromContext(r.Context())
    if err != nil {
        http.Error(w, "No scope", http.StatusInternalServerError)
        return
    }

    service := godi.MustResolve[*UserService](scope)
    users := service.GetAll()
    json.NewEncoder(w).Encode(users)
})

Subrouters

Apply different middleware to subrouters:

r := chi.NewRouter()
r.Use(godichi.ScopeMiddleware(provider))

// Public routes
r.Group(func(r chi.Router) {
    r.Get("/health", healthHandler)
    r.Get("/public", publicHandler)
})

// Authenticated routes
r.Group(func(r chi.Router) {
    r.Use(authMiddleware)
    r.Get("/users", godichi.Handle((*UserController).List))
})

See also: Gin Integration | Echo Integration | Fiber Integration | net/http Integration