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
| Middleware | Description | Usage |
|---|---|---|
Logger | HTTP request logger | e.Use(middleware.Logger()) |
Recover | Panic recovery | e.Use(middleware.Recover()) |
CORS | Cross-Origin Resource Sharing | e.Use(middleware.CORS()) |
Gzip | Gzip compression | e.Use(middleware.Gzip()) |
BasicAuth | HTTP Basic authentication | e.Use(middleware.BasicAuth(fn)) |
JWT | JSON Web Token | e.Use(middleware.JWT([]byte(secret))) |
RateLimiter | Rate limiting | e.Use(middleware.RateLimiter(...)) |
BodyLimit | Request body size limit | e.Use(middleware.BodyLimit("2M")) |
Secure | Security headers | e.Use(middleware.Secure()) |
RequestID | Unique request ID | e.Use(middleware.RequestID()) |
Static | Static file serving | e.Use(middleware.Static("/public")) |
Timeout | Request timeout | e.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
| Problem | Solution |
|---|---|
code=404, message=Not Found | Check route registration; verify HTTP method matches |
| Middleware order issues | Register global middleware before routes; group middleware in group |
| Binding fails | Check struct tags match content type (json:, form:, query:) |
| CORS errors | Add CORS middleware; check allowed origins and methods |
| Context deadline exceeded | Increase server timeouts; check handler for long operations |
code=405, Method Not Allowed | Route exists but wrong HTTP method; check route definitions |
| Validator not working | Register custom validator: e.Validator = &CustomValidator{} |
| Static files 404 | Use e.Static("/", "public") or middleware.Static() |