Ir al contenido

Echo Go Cheat Sheet

Overview

Echo is a high-performance, extensible, minimalist web framework for Go. It features an optimized HTTP router with zero dynamic memory allocation, automatic TLS via Let’s Encrypt, HTTP/2 support, and a comprehensive middleware ecosystem. Echo follows a design philosophy of being fast yet expressive, providing just enough abstraction without sacrificing Go’s performance characteristics.

Built and maintained by LabStack, Echo has become one of the top Go web frameworks alongside Gin and Fiber. It provides a clean API for building RESTful services with built-in support for data binding, validation, rendering, and extensible middleware. Echo’s router uses a radix tree structure for optimal routing performance and supports both static and parameterized routes with priorities.

Installation

Setup

# Initialize Go module
mkdir my-api && cd my-api
go mod init my-api

# Install Echo v4
go get github.com/labstack/echo/v4
go get github.com/labstack/echo/v4/middleware

# Run
go run main.go

Minimal Server

package main

import (
    "net/http"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    e := echo.New()

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

    // Routes
    e.GET("/", func(c echo.Context) error {
        return c.JSON(http.StatusOK, map[string]string{"message": "Hello, World!"})
    })

    e.Logger.Fatal(e.Start(":8080"))
}

Routing

HTTP Methods

e.GET("/users", getUsers)
e.POST("/users", createUser)
e.PUT("/users/:id", updateUser)
e.PATCH("/users/:id", patchUser)
e.DELETE("/users/:id", deleteUser)

// Match any method
e.Any("/any", handleAny)

// Match specific methods
e.Match([]string{"GET", "POST"}, "/match", handler)

Path Parameters

// Named parameter :name
e.GET("/users/:id", func(c echo.Context) error {
    id := c.Param("id")
    return c.JSON(200, map[string]string{"id": id})
})

// Wildcard parameter *
e.GET("/files/*", func(c echo.Context) error {
    path := c.Param("*")
    return c.String(200, path)
})

// Multiple parameters
e.GET("/users/:userId/posts/:postId", func(c echo.Context) error {
    userId := c.Param("userId")
    postId := c.Param("postId")
    return c.JSON(200, map[string]string{
        "userId": userId,
        "postId": postId,
    })
})

Route Groups

// Basic grouping
api := e.Group("/api")
api.GET("/users", getUsers)
api.POST("/users", createUser)

// Versioned API
v1 := e.Group("/api/v1")
v1.GET("/users", getUsersV1)

v2 := e.Group("/api/v2")
v2.GET("/users", getUsersV2)

// Group with middleware
admin := e.Group("/admin", middleware.BasicAuth(validateAdmin))
admin.GET("/dashboard", adminDashboard)
admin.GET("/stats", adminStats)

// Nested groups
api = e.Group("/api")
public := api.Group("/public")
public.GET("/info", getInfo)

private := api.Group("/private")
private.Use(JWTMiddleware())
private.GET("/profile", getProfile)

Request Handling

Query Parameters

e.GET("/search", func(c echo.Context) error {
    q := c.QueryParam("q")
    page := c.QueryParam("page")
    
    // With default
    limit := c.QueryParam("limit")
    if limit == "" {
        limit = "20"
    }

    // All query params
    params := c.QueryParams() // url.Values

    return c.JSON(200, map[string]string{
        "query": q,
        "page":  page,
        "limit": limit,
    })
})

Request Binding

type CreateUserRequest struct {
    Name     string `json:"name" form:"name" validate:"required"`
    Email    string `json:"email" form:"email" validate:"required,email"`
    Age      int    `json:"age" form:"age" validate:"gte=0,lte=150"`
}

e.POST("/users", func(c echo.Context) error {
    req := new(CreateUserRequest)
    if err := c.Bind(req); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    // Validate (requires custom validator)
    if err := c.Validate(req); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    }
    return c.JSON(http.StatusCreated, req)
})

Custom Validator

import "github.com/go-playground/validator/v10"

type CustomValidator struct {
    validator *validator.Validate
}

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

// Register
e.Validator = &CustomValidator{validator: validator.New()}

Form Data

e.POST("/login", func(c echo.Context) error {
    username := c.FormValue("username")
    password := c.FormValue("password")
    return c.JSON(200, map[string]string{"user": username})
})

Response Types

// JSON
c.JSON(200, user)
c.JSONPretty(200, user, "  ")

// XML
c.XML(200, user)

// String
c.String(200, "Hello, World!")

// HTML
c.HTML(200, "<h1>Hello</h1>")

// File
c.File("/path/to/file")
c.Attachment("/path/to/file", "download.pdf")
c.Inline("/path/to/file", "image.png")

// Stream
c.Stream(200, "application/octet-stream", reader)

// Blob
c.Blob(200, "application/octet-stream", data)

// Redirect
c.Redirect(http.StatusMovedPermanently, "/new-url")

// No content
c.NoContent(http.StatusNoContent)

// Set headers
c.Response().Header().Set("X-Custom", "value")

// Set cookie
cookie := new(http.Cookie)
cookie.Name = "session"
cookie.Value = "abc123"
cookie.Expires = time.Now().Add(24 * time.Hour)
c.SetCookie(cookie)

Middleware

Built-in Middleware

MiddlewareDescriptionUsage
LoggerHTTP request loggere.Use(middleware.Logger())
RecoverPanic recoverye.Use(middleware.Recover())
CORSCross-Origin Resource Sharinge.Use(middleware.CORS())
GzipGzip compressione.Use(middleware.Gzip())
BasicAuthHTTP Basic authenticatione.Use(middleware.BasicAuth(fn))
JWTJSON Web Tokene.Use(middleware.JWT([]byte(secret)))
RateLimiterRate limitinge.Use(middleware.RateLimiter(...))
BodyLimitRequest body size limite.Use(middleware.BodyLimit("2M"))
SecureSecurity headerse.Use(middleware.Secure())
RequestIDUnique request IDe.Use(middleware.RequestID())
StaticStatic file servinge.Use(middleware.Static("/public"))
TimeoutRequest timeoute.Use(middleware.Timeout())

CORS Configuration

e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins: []string{"https://example.com"},
    AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.DELETE},
    AllowHeaders: []string{echo.HeaderContentType, echo.HeaderAuthorization},
    MaxAge:       3600,
}))

Custom Middleware

func RequestTimer(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
    }
}

func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        token := c.Request().Header.Get("Authorization")
        if token == "" {
            return echo.NewHTTPError(401, "Missing authorization")
        }
        user, err := validateToken(token)
        if err != nil {
            return echo.NewHTTPError(401, "Invalid token")
        }
        c.Set("user", user)
        return next(c)
    }
}

// Apply
e.Use(RequestTimer)

Configuration

Server Configuration

e := echo.New()

// Server settings
e.Server.ReadTimeout = 10 * time.Second
e.Server.WriteTimeout = 30 * time.Second
e.Server.IdleTimeout = 120 * time.Second

// Custom error handler
e.HTTPErrorHandler = func(err error, c echo.Context) {
    code := http.StatusInternalServerError
    message := "Internal Server Error"

    if he, ok := err.(*echo.HTTPError); ok {
        code = he.Code
        message = fmt.Sprintf("%v", he.Message)
    }

    c.JSON(code, map[string]interface{}{
        "error":   true,
        "message": message,
        "code":    code,
    })
}

// Debug mode
e.Debug = true

// Auto TLS (Let's Encrypt)
e.AutoTLSManager.Cache = autocert.DirCache("/var/www/.cache")
e.Logger.Fatal(e.StartAutoTLS(":443"))

Template Rendering

import "html/template"

type Template struct {
    templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
    return t.templates.ExecuteTemplate(w, name, data)
}

e.Renderer = &Template{
    templates: template.Must(template.ParseGlob("views/*.html")),
}

e.GET("/", func(c echo.Context) error {
    return c.Render(200, "index.html", map[string]interface{}{
        "title": "Home",
    })
})

Advanced Usage

File Upload

e.POST("/upload", func(c echo.Context) error {
    file, err := c.FormFile("file")
    if err != nil {
        return err
    }

    src, err := file.Open()
    if err != nil {
        return err
    }
    defer src.Close()

    dst, err := os.Create(filepath.Join("uploads", file.Filename))
    if err != nil {
        return err
    }
    defer dst.Close()

    if _, err = io.Copy(dst, src); err != nil {
        return err
    }

    return c.JSON(200, map[string]string{
        "filename": file.Filename,
        "size":     fmt.Sprintf("%d", file.Size),
    })
})

WebSocket

import "github.com/gorilla/websocket"

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },
}

e.GET("/ws", func(c echo.Context) error {
    ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil)
    if err != nil {
        return err
    }
    defer ws.Close()

    for {
        _, msg, err := ws.ReadMessage()
        if err != nil {
            break
        }
        ws.WriteMessage(websocket.TextMessage, msg)
    }
    return nil
})

Graceful Shutdown

func main() {
    e := echo.New()
    e.GET("/", handler)

    go func() {
        if err := e.Start(":8080"); err != nil && err != http.ErrServerClosed {
            e.Logger.Fatal("shutting down the server")
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt)
    <-quit

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    if err := e.Shutdown(ctx); err != nil {
        e.Logger.Fatal(err)
    }
}

Troubleshooting

ProblemSolution
code=404, message=Not FoundCheck route registration; verify HTTP method matches
Middleware order issuesRegister global middleware before routes; group middleware in group
Binding failsCheck struct tags match content type (json:, form:, query:)
CORS errorsAdd CORS middleware; check allowed origins and methods
Context deadline exceededIncrease server timeouts; check handler for long operations
code=405, Method Not AllowedRoute exists but wrong HTTP method; check route definitions
Validator not workingRegister custom validator: e.Validator = &CustomValidator{}
Static files 404Use e.Static("/", "public") or middleware.Static()