Skip to main content
โšก Calmops

Echo Framework: Building Web Applications

Echo Framework: Building Web Applications

Introduction

Echo is a high-performance, extensible web framework for Go. It provides a clean API for building web applications with excellent routing, middleware support, and built-in features like validation and error handling. This guide covers building production-ready web applications with Echo.

Core Concepts

Echo Features

  • Fast HTTP routing with path parameters
  • Middleware support for request processing
  • Built-in validation and binding
  • Automatic error handling
  • WebSocket support
  • Template rendering

Good: Basic Echo Setup

Creating an Echo Application

package main

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

// โœ… GOOD: Basic Echo application
func main() {
	e := echo.New()

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

	// Routes
	e.GET("/", handleHome)
	e.GET("/users/:id", handleGetUser)
	e.POST("/users", handleCreateUser)

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

func handleHome(c echo.Context) error {
	return c.JSON(http.StatusOK, map[string]string{
		"message": "Welcome to Echo",
	})
}

func handleGetUser(c echo.Context) error {
	id := c.Param("id")
	return c.JSON(http.StatusOK, map[string]string{
		"id": id,
	})
}

func handleCreateUser(c echo.Context) error {
	user := struct {
		Name  string `json:"name"`
		Email string `json:"email"`
	}{}

	if err := c.Bind(&user); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": err.Error(),
		})
	}

	return c.JSON(http.StatusCreated, user)
}

Route Groups

package main

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

// โœ… GOOD: Route grouping in Echo
func setupRoutes(e *echo.Echo) {
	// API v1
	v1 := e.Group("/api/v1")
	{
		v1.GET("/health", handleHealth)
		v1.GET("/users", handleListUsers)
		v1.POST("/users", handleCreateUser)
	}

	// API v2
	v2 := e.Group("/api/v2")
	{
		v2.GET("/health", handleHealthV2)
		v2.GET("/users", handleListUsersV2)
	}

	// Admin routes
	admin := e.Group("/admin")
	admin.Use(authMiddleware())
	{
		admin.GET("/stats", handleStats)
		admin.DELETE("/users/:id", handleDeleteUser)
	}
}

func handleHealth(c echo.Context) error {
	return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
}

func handleListUsers(c echo.Context) error {
	return c.JSON(http.StatusOK, []string{})
}

func handleCreateUser(c echo.Context) error {
	return c.JSON(http.StatusCreated, map[string]bool{"created": true})
}

func handleHealthV2(c echo.Context) error {
	return c.JSON(http.StatusOK, map[string]string{"status": "ok", "version": "2"})
}

func handleListUsersV2(c echo.Context) error {
	return c.JSON(http.StatusOK, []string{})
}

func handleStats(c echo.Context) error {
	return c.JSON(http.StatusOK, map[string]interface{}{"stats": "data"})
}

func handleDeleteUser(c echo.Context) error {
	return c.JSON(http.StatusOK, map[string]bool{"deleted": true})
}

func authMiddleware() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			// Check auth
			return next(c)
		}
	}
}

Good: Middleware

Creating Echo Middleware

package main

import (
	"github.com/labstack/echo/v4"
	"log"
	"time"
)

// โœ… GOOD: Logging middleware
func loggingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		start := time.Now()

		err := next(c)

		duration := time.Since(start)
		log.Printf("%s %s %d %v",
			c.Request().Method,
			c.Request().URL.Path,
			c.Response().Status,
			duration,
		)

		return err
	}
}

// โœ… GOOD: Authentication middleware
func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		token := c.Request().Header.Get("Authorization")
		if token == "" {
			return c.JSON(401, map[string]string{"error": "Unauthorized"})
		}

		// Validate token
		c.Set("user_id", 123)
		return next(c)
	}
}

// โœ… GOOD: CORS middleware
func corsMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		c.Response().Header().Set("Access-Control-Allow-Origin", "*")
		c.Response().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
		return next(c)
	}
}

// โœ… GOOD: Error handling middleware
func errorHandlingMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		defer func() {
			if err := recover(); err != nil {
				log.Printf("Panic: %v", err)
				c.JSON(500, map[string]string{"error": "Internal server error"})
			}
		}()
		return next(c)
	}
}

Good: Request Handling

Binding and Validation

package main

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

// โœ… GOOD: Struct with validation tags
type User struct {
	Name  string `json:"name" validate:"required,min=3,max=50"`
	Email string `json:"email" validate:"required,email"`
	Age   int    `json:"age" validate:"min=0,max=150"`
}

// โœ… GOOD: Bind and validate JSON
func handleCreateUserWithValidation(c echo.Context) error {
	user := new(User)

	if err := c.Bind(user); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "Invalid request",
		})
	}

	// Validate
	if err := c.Validate(user); err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": err.Error(),
		})
	}

	return c.JSON(http.StatusCreated, user)
}

// โœ… GOOD: Extract query parameters
func handleSearch(c echo.Context) error {
	query := c.QueryParam("q")
	limit := c.QueryParam("limit")

	return c.JSON(http.StatusOK, map[string]string{
		"query": query,
		"limit": limit,
	})
}

// โœ… GOOD: Extract path parameters
func handleGetUserByID(c echo.Context) error {
	id := c.Param("id")
	return c.JSON(http.StatusOK, map[string]string{
		"id": id,
	})
}

// โœ… GOOD: Set response headers
func handleWithHeaders(c echo.Context) error {
	c.Response().Header().Set("X-Custom-Header", "value")
	return c.JSON(http.StatusOK, map[string]string{"message": "ok"})
}

Advanced Patterns

Custom Error Handler

package main

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

// โœ… GOOD: Custom error handler
type ErrorResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Details string `json:"details,omitempty"`
}

func customErrorHandler(err error, c echo.Context) {
	code := http.StatusInternalServerError
	message := "Internal server error"

	if he, ok := err.(*echo.HTTPError); ok {
		code = he.Code
		message = he.Message.(string)
	}

	c.JSON(code, ErrorResponse{
		Code:    code,
		Message: message,
	})
}

// โœ… GOOD: Using custom error handler
func setupErrorHandling(e *echo.Echo) {
	e.HTTPErrorHandler = customErrorHandler
}

File Upload

package main

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

// โœ… GOOD: Handle file upload
func handleFileUpload(c echo.Context) error {
	file, err := c.FormFile("file")
	if err != nil {
		return c.JSON(http.StatusBadRequest, map[string]string{
			"error": "No file provided",
		})
	}

	// Save file
	src, _ := file.Open()
	defer src.Close()

	dst, _ := c.Response().Writer.(http.ResponseWriter)
	if _, err := dst.Write([]byte("File saved")); err != nil {
		return err
	}

	return c.JSON(http.StatusOK, map[string]interface{}{
		"filename": file.Filename,
		"size":     file.Size,
	})
}

Best Practices

1. Use Middleware for Cross-Cutting Concerns

// โœ… GOOD: Middleware for logging, auth, etc.
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(authMiddleware())

// โŒ BAD: Repeating logic in handlers
func handler(c echo.Context) error {
	// Log
	// Check auth
	// Do work
}

2. Validate Input

// โœ… GOOD: Use validation tags
type Request struct {
	Email string `json:"email" validate:"required,email"`
	Age   int    `json:"age" validate:"min=0,max=150"`
}

// โŒ BAD: Manual validation
type BadRequest struct {
	Email string
	Age   int
}

3. Handle Errors Consistently

// โœ… GOOD: Consistent error responses
return c.JSON(http.StatusBadRequest, ErrorResponse{
	Code:    http.StatusBadRequest,
	Message: "Invalid input",
})

// โŒ BAD: Inconsistent error handling
return c.String(http.StatusBadRequest, "Error")

Resources

Summary

Echo is a powerful framework for building web applications in Go. Use route groups for organization, middleware for cross-cutting concerns, and proper validation for robust applications. Echo’s simplicity and extensibility make it ideal for building scalable web services.

Comments