echo

Applies to: Echo v4+, REST APIs, Microservices, High-Performance Web Applications Language Guide: @.claude/skills/go-guide/SKILL.md

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "echo" with this command: npx skills add ar4mirez/samuel/ar4mirez-samuel-echo

Echo Framework Guide

Applies to: Echo v4+, REST APIs, Microservices, High-Performance Web Applications Language Guide: @.claude/skills/go-guide/SKILL.md

Overview

Echo is a high-performance, extensible, minimalist Go web framework. It features an optimized HTTP router, middleware support, data binding, and rendering.

Use Echo when:

  • You need high performance with minimal overhead

  • You want a clean, intuitive API

  • Built-in middleware matters (JWT, CORS, Gzip, etc.)

  • You prefer automatic TLS via Let's Encrypt

Consider alternatives when:

  • You want the most popular framework (use Gin)

  • You need WebSocket support built-in (use Fiber)

  • Maximum community resources are needed (use Gin)

Project Structure

myproject/ ├── cmd/ │ └── api/ │ └── main.go # Entry point ├── internal/ │ ├── config/ │ │ └── config.go # Configuration │ ├── handler/ │ │ ├── handler.go # Handler container │ │ ├── user.go # User handlers │ │ └── auth.go # Auth handlers │ ├── middleware/ │ │ ├── auth.go # JWT middleware │ │ └── custom.go # Custom middleware │ ├── model/ │ │ ├── user.go # User model │ │ └── response.go # Response models │ ├── repository/ │ │ ├── repository.go # Repository interface │ │ └── user.go # User repository │ ├── service/ │ │ ├── service.go # Service container │ │ └── user.go # User service │ └── validator/ │ └── validator.go # Custom validators ├── pkg/ │ └── response/ │ └── response.go # Response helpers ├── migrations/ ├── .env.example ├── go.mod ├── go.sum ├── Makefile └── README.md

  • cmd/ for entry points; one main.go per binary

  • internal/ for private application code; not importable externally

  • handler/ contains Echo handler functions, thin HTTP layer only

  • service/ holds business logic; no Echo or HTTP dependencies

  • repository/ encapsulates data access; accepts context.Context

  • model/ defines domain structs, request/response DTOs

  • middleware/ for custom Echo middleware functions

  • validator/ for custom validation rules via go-playground/validator

Application Setup

e := echo.New() e.HideBanner = true e.Validator = validator.NewCustomValidator()

// Global middleware (order matters) e.Use(echoMw.Recover()) // First: catch panics e.Use(echoMw.Logger()) // Second: log all requests e.Use(echoMw.RequestID()) // Third: trace IDs e.Use(echoMw.CORSWithConfig(echoMw.CORSConfig{ AllowOrigins: cfg.CORSOrigins, AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete}, AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization}, }))

Setup guidelines:

  • Set e.HideBanner = true in production

  • Register Recover() first to catch panics in all handlers

  • Use RequestID() for distributed tracing

  • Always implement graceful shutdown with e.Shutdown(ctx)

  • See references/patterns.md for full main.go with graceful shutdown

Routing

Route Groups and Versioning

func setupRoutes(e *echo.Echo, h *handler.Handlers, authMw *middleware.AuthMiddleware) { // Health check (ungrouped) e.GET("/health", func(c echo.Context) error { return c.JSON(http.StatusOK, map[string]string{"status": "ok"}) })

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

// Public routes
auth := v1.Group("/auth")
auth.POST("/login", h.Auth.Login)
auth.POST("/register", h.Auth.Register)
auth.POST("/refresh", h.Auth.Refresh)

// Protected routes
users := v1.Group("/users")
users.POST("", h.User.Create)
users.Use(authMw.Authenticate)
users.GET("", h.User.GetAll)
users.GET("/me", h.User.GetCurrent)
users.GET("/:id", h.User.GetByID)
users.PUT("/:id", h.User.Update)
users.DELETE("/:id", h.User.Delete, authMw.RequireAdmin)

}

Routing guidelines:

  • Group routes by resource and version (/api/v1/users )

  • Apply middleware at group level, not per-route when possible

  • Use /:param for path parameters (e.g., /:id )

  • Append per-route middleware as extra handler args (e.g., RequireAdmin )

  • Keep health check outside API groups for monitoring tools

Middleware

Built-in Middleware (use these first)

Middleware Purpose

Recover()

Catch panics, return 500

Logger()

Request logging

RequestID()

Unique request IDs

CORS()

Cross-origin requests

Gzip()

Response compression

RateLimiter()

Rate limiting

Secure()

Security headers

BodyLimit()

Request size limits

Custom Middleware Pattern

// Middleware that injects request-scoped values func RequestTimerMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { start := time.Now() err := next(c) duration := time.Since(start) c.Response().Header().Set("X-Response-Time", duration.String()) return err } }

// Middleware with configuration type RateLimitConfig struct { Rate int Burst int }

func RateLimitMiddleware(cfg RateLimitConfig) echo.MiddlewareFunc { limiter := rate.NewLimiter( rate.Limit(cfg.Rate), cfg.Burst, ) return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { if !limiter.Allow() { return c.JSON(http.StatusTooManyRequests, model.ErrorResponse("rate limit exceeded")) } return next(c) } } }

Middleware guidelines:

  • Return echo.HandlerFunc from middleware

  • Call next(c) to continue the chain; skip to short-circuit

  • Configurable middleware returns echo.MiddlewareFunc

  • Order matters: register Recover() first, then logging, then auth

Echo Context

Reading Values

func handler(c echo.Context) error { // Path params id := c.Param("id")

// Query params
page := c.QueryParam("page")
search := c.QueryParam("q")

// Form values
name := c.FormValue("name")

// Headers
token := c.Request().Header.Get("Authorization")

// Request-scoped values (set by middleware)
userID, ok := c.Get("user_id").(uint)

// Real IP (respects X-Forwarded-For)
ip := c.RealIP()

// Underlying context.Context for DB/service calls
ctx := c.Request().Context()

return c.JSON(http.StatusOK, data)

}

Context guidelines:

  • Use c.Request().Context() when passing to services/repositories

  • Use c.Get() /c.Set() for request-scoped values between middleware and handlers

  • Type-assert values from c.Get() with ok check

  • Never store echo.Context beyond the request lifecycle

Request Binding and Validation

Binding

type CreateUserRequest struct { Email string json:"email" validate:"required,email" Password string json:"password" validate:"required,min=8" FirstName string json:"first_name" validate:"required,min=1,max=100" LastName string json:"last_name" validate:"required,min=1,max=100" }

func (h *UserHandler) Create(c echo.Context) error { var req CreateUserRequest if err := c.Bind(&req); err != nil { return c.JSON(http.StatusBadRequest, model.ErrorResponse(err.Error())) } if err := c.Validate(&req); err != nil { return err // Custom validator returns HTTPError } // Process valid request... }

Custom Validator

type CustomValidator struct { validator *validator.Validate }

func NewCustomValidator() *CustomValidator { v := validator.New() v.RegisterTagNameFunc(func(fld reflect.StructField) string { name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] if name == "-" { return "" } return name }) return &CustomValidator{validator: v} }

func (cv *CustomValidator) Validate(i interface{}) error { if err := cv.validator.Struct(i); err != nil { return echo.NewHTTPError( http.StatusBadRequest, formatValidationErrors(err), ) } return nil }

Binding guidelines:

  • Always Bind then Validate as separate steps

  • Use struct tags: json for binding, validate for rules

  • Register custom validator on e.Validator at startup

  • Return structured validation errors, not raw messages

  • Use pointer fields for optional/partial updates (*string )

Response Patterns

Use a consistent JSON envelope across all endpoints:

// Success: c.JSON(http.StatusOK, SuccessResponse(data)) // Error: c.JSON(http.StatusNotFound, ErrorResponse("not found")) // Delete: c.NoContent(http.StatusNoContent)

type Response struct { Success bool json:"success" Data interface{} json:"data,omitempty" Error string json:"error,omitempty" }

func SuccessResponse(data interface{}) *Response { return &Response{Success: true, Data: data} }

func ErrorResponse(msg string) *Response { return &Response{Success: false, Error: msg} }

  • Use response DTOs; never expose domain models directly (omit passwords, internal IDs)

  • For paginated responses, include meta with page/total counts

  • See references/patterns.md for PaginatedResponse struct

Error Handling

Domain Errors

var ( ErrUserNotFound = errors.New("user not found") ErrUserAlreadyExists = errors.New("user already exists") ErrInvalidCredentials = errors.New("invalid credentials") )

Handler Error Mapping

func (h *UserHandler) GetByID(c echo.Context) error { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { return c.JSON(http.StatusBadRequest, model.ErrorResponse("invalid user ID")) }

user, err := h.userService.GetByID(
    c.Request().Context(), uint(id),
)
if err != nil {
    if errors.Is(err, service.ErrUserNotFound) {
        return c.JSON(http.StatusNotFound,
            model.ErrorResponse(err.Error()))
    }
    return c.JSON(http.StatusInternalServerError,
        model.ErrorResponse(err.Error()))
}

return c.JSON(http.StatusOK,
    model.SuccessResponse(user.ToResponse()))

}

Error handling guidelines:

  • Define domain errors as sentinel values in the service layer

  • Map domain errors to HTTP status codes in handlers only

  • Use errors.Is() / errors.As() for error checking

  • Never expose internal errors to clients in production

  • Log detailed errors server-side; return safe messages to clients

Configuration

  • Load from environment variables (12-factor app) via godotenv

  • Provide sensible defaults for development only

  • Never hardcode secrets; use empty defaults to force explicit config

  • Parse durations and validate at startup, not at use time

  • See references/patterns.md for full config struct example

Commands Reference

Initialize project

go mod init myproject

Install dependencies

go mod tidy

Run development server

go run cmd/api/main.go

Build binary

go build -o bin/api cmd/api/main.go

Run tests

go test ./... go test -v -cover ./...

Run with race detection

go test -race ./...

Lint

golangci-lint run

Database migrations (golang-migrate)

migrate -path migrations -database "$DATABASE_URL" up migrate -path migrations -database "$DATABASE_URL" down

Dependencies

// Core github.com/labstack/echo/v4 // Web framework github.com/go-playground/validator/v10 // Validation github.com/golang-jwt/jwt/v5 // JWT auth github.com/joho/godotenv // Env loading

// Database gorm.io/gorm // ORM gorm.io/driver/postgres // PostgreSQL driver

// Crypto golang.org/x/crypto // bcrypt, etc.

// Testing github.com/stretchr/testify // Assertions and mocks

Best Practices Checklist

Echo-Specific

  • Use custom validator with go-playground/validator

  • Use echo.Context for request handling only (not in services)

  • Use middleware groups for route organization

  • Return errors from handlers for centralized handling

  • Use echo.Bind for request binding

  • Configure proper timeouts and graceful shutdown

  • Set e.HideBanner = true in production

Performance

  • Configure database connection pooling (SetMaxOpenConns , etc.)

  • Implement pagination for list endpoints

  • Use Gzip() middleware for response compression

  • Implement request logging middleware for observability

  • Use BodyLimit() to prevent oversized requests

Advanced Topics

For detailed patterns and examples, see:

  • references/patterns.md -- Handler examples, database repository, authentication, WebSocket, testing patterns

External References

  • Echo Documentation

  • Echo GitHub

  • Echo Cookbook

  • GORM Documentation

  • go-playground/validator

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

actix-web

No summary provided by upstream source.

Repository SourceNeeds Review
General

frontend-design

No summary provided by upstream source.

Repository SourceNeeds Review
General

blazor

No summary provided by upstream source.

Repository SourceNeeds Review
General

assembly-guide

No summary provided by upstream source.

Repository SourceNeeds Review