Echo Integration

Complete guide for using godi with the Echo web framework.

Installation

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

Quick Start

package main

import (
    "net/http"

    "github.com/labstack/echo/v4"
    "github.com/junioryono/godi/v4"
    godiecho "github.com/junioryono/godi/v4/echo"
)

type UserController struct{}

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

func (c *UserController) List(ctx echo.Context) error {
    return ctx.JSON(http.StatusOK, []string{"alice", "bob"})
}

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

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

    e := echo.New()
    e.Use(godiecho.ScopeMiddleware(provider))
    e.GET("/users", godiecho.Handle((*UserController).List))

    e.Start(":8080")
}

ScopeMiddleware

Creates a request scope for each HTTP request.

e := echo.New()
e.Use(godiecho.ScopeMiddleware(provider))

Configuration Options

e.Use(godiecho.ScopeMiddleware(provider,
    // Custom error handler for scope creation failures
    godiecho.WithErrorHandler(func(c echo.Context, err error) error {
        return echo.NewHTTPError(http.StatusServiceUnavailable, "Service unavailable")
    }),

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

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

Handle

Wraps a controller method for type-safe resolution.

type UserController interface {
    List(echo.Context) error
    GetByID(echo.Context) error
    Create(echo.Context) error
}

e.GET("/users", godiecho.Handle(UserController.List))
e.GET("/users/:id", godiecho.Handle(UserController.GetByID))
e.POST("/users", godiecho.Handle(UserController.Create))

Handler Options

e.GET("/users", godiecho.Handle(UserController.List,
    // Enable panic recovery
    godiecho.WithPanicRecovery(true),

    // Custom panic handler
    godiecho.WithPanicHandler(func(c echo.Context, v any) error {
        log.Printf("Panic: %v", v)
        return echo.NewHTTPError(http.StatusInternalServerError, "Unexpected error")
    }),

    // Custom scope error handler
    godiecho.WithScopeErrorHandler(func(c echo.Context, err error) error {
        return echo.NewHTTPError(http.StatusInternalServerError, "Session error")
    }),

    // Custom resolution error handler
    godiecho.WithResolutionErrorHandler(func(c echo.Context, err error) error {
        return echo.NewHTTPError(http.StatusServiceUnavailable, "Service unavailable")
    }),
))

Complete Example

package main

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

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
    "github.com/google/uuid"
    "github.com/junioryono/godi/v4"
    godiecho "github.com/junioryono/godi/v4/echo"
)

// === 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(ctx echo.Context) error {
    users := c.service.GetAll()
    ctx.Response().Header().Set("X-Request-ID", c.reqCtx.ID)
    return ctx.JSON(http.StatusOK, map[string]any{
        "users":    users,
        "duration": time.Since(c.reqCtx.StartTime).String(),
    })
}

func (c *UserController) GetByID(ctx echo.Context) error {
    id := ctx.Param("id")
    ctx.Response().Header().Set("X-Request-ID", c.reqCtx.ID)
    return ctx.JSON(http.StatusOK, 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 Echo instance
    e := echo.New()

    // Echo middleware
    e.Use(middleware.Logger())
    e.Use(middleware.Recover())

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

    // Routes
    e.GET("/users", godiecho.Handle((*UserController).List))
    e.GET("/users/:id", godiecho.Handle((*UserController).GetByID))

    // Health check
    e.GET("/health", func(c echo.Context) error {
        return c.String(http.StatusOK, "OK")
    })

    // Start server
    log.Println("Server starting on :8080")
    e.Start(":8080")
}

Route Groups

Use with Echo route groups:

api := e.Group("/api/v1")

users := api.Group("/users")
users.GET("", godiecho.Handle((*UserController).List))
users.GET("/:id", godiecho.Handle((*UserController).GetByID))
users.POST("", godiecho.Handle((*UserController).Create))

orders := api.Group("/orders")
orders.GET("", godiecho.Handle((*OrderController).List))
orders.POST("", godiecho.Handle((*OrderController).Create))

Accessing URL Parameters

Use Echo’s parameter methods in your controllers:

func (c *UserController) GetByID(ctx echo.Context) error {
    id := ctx.Param("id")
    // ...
}

Accessing Scope Manually

e.GET("/custom", func(c echo.Context) error {
    scope, err := godi.FromContext(c.Request().Context())
    if err != nil {
        return echo.NewHTTPError(http.StatusInternalServerError, "No scope")
    }

    service := godi.MustResolve[*UserService](scope)
    users := service.GetAll()
    return c.JSON(http.StatusOK, users)
})

Request Binding

Combine godi with Echo’s request binding:

type CreateUserRequest struct {
    Name  string `json:"name" validate:"required"`
    Email string `json:"email" validate:"required,email"`
}

func (c *UserController) Create(ctx echo.Context) error {
    var req CreateUserRequest
    if err := ctx.Bind(&req); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, "Invalid request")
    }

    user := c.service.Create(req.Name, req.Email)
    return ctx.JSON(http.StatusCreated, user)
}

Error Handling

Echo uses echo.HTTPError for error responses:

godiecho.WithResolutionErrorHandler(func(c echo.Context, err error) error {
    return echo.NewHTTPError(http.StatusServiceUnavailable, map[string]any{
        "error":   "Service temporarily unavailable",
        "details": err.Error(),
    })
})

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