Skip to main content

Echo Middleware Groups

Introduction

When building web applications with Echo framework in Go, you'll often need to apply different middleware to different routes. As your application grows, managing which middleware applies to which routes can become complex. Echo Middleware Groups provides a solution to this problem by allowing you to organize and apply middleware to groups of routes, making your code more maintainable and structured.

Middleware groups let you:

  • Apply the same set of middleware to multiple routes at once
  • Create logical sections in your application
  • Keep your routing code clean and organized
  • Apply different authentication or processing rules to different parts of your application

Understanding Middleware Groups

Middleware groups in Echo work by creating a group of routes that share common middleware. This is done through the Group() method, which returns a new Group object that can have its own middleware and routes.

Basic Structure of a Middleware Group

go
// Create a new Echo instance
e := echo.New()

// Create a group with a prefix and middleware
adminGroup := e.Group("/admin", middleware.Logger(), middleware.BasicAuth())

// Add routes to the group
adminGroup.GET("/dashboard", dashboardHandler)
adminGroup.POST("/users", createUserHandler)

In the example above, all routes under the /admin prefix will automatically use both the Logger middleware and BasicAuth middleware.

Creating Middleware Groups

Let's look at some practical examples of creating and using middleware groups.

Example 1: Basic Middleware Group

go
package main

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

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

// Global middleware
e.Use(middleware.Recover())

// Public routes without authentication
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Welcome to the homepage!")
})

// Create an API group with additional middleware
api := e.Group("/api", middleware.Logger())

// Routes within the API group
api.GET("/users", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "Users list",
})
})

// Create a nested admin group with authentication
admin := api.Group("/admin", middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
// Simple authentication logic
if username == "admin" && password == "secret" {
return true, nil
}
return false, nil
}))

admin.GET("/dashboard", func(c echo.Context) error {
return c.String(http.StatusOK, "Admin Dashboard")
})

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

In this example:

  1. All routes use the Recover middleware
  2. The /api routes use both Recover and Logger middleware
  3. The /api/admin routes use Recover, Logger, and BasicAuth middleware

Example 2: Organizing API Versions

Middleware groups are excellent for API versioning:

go
package main

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

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

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

// API v1 group
v1 := e.Group("/v1")
v1.GET("/products", getProductsV1)
v1.GET("/users", getUsersV1)

// API v2 group with rate limiting middleware
v2 := e.Group("/v2", middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
v2.GET("/products", getProductsV2)
v2.GET("/users", getUsersV2)

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

func getProductsV1(c echo.Context) error {
return c.String(http.StatusOK, "Products API v1")
}

func getUsersV1(c echo.Context) error {
return c.String(http.StatusOK, "Users API v1")
}

func getProductsV2(c echo.Context) error {
return c.String(http.StatusOK, "Products API v2 - Enhanced")
}

func getUsersV2(c echo.Context) error {
return c.String(http.StatusOK, "Users API v2 - Enhanced")
}

This example creates two API versions, with the newer v2 API having rate limiting middleware applied.

Advanced Group Techniques

Nested Middleware Groups

You can create deeply nested middleware groups for more complex applications:

go
package main

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

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

// API group
api := e.Group("/api", middleware.Logger())

// Management sub-group with CORS
mgmt := api.Group("/management", middleware.CORS())

// Reports sub-sub-group with JWT authentication
reports := mgmt.Group("/reports", middleware.JWT([]byte("secret")))

reports.GET("/sales", func(c echo.Context) error {
return c.String(http.StatusOK, "Sales Reports")
})

reports.GET("/inventory", func(c echo.Context) error {
return c.String(http.StatusOK, "Inventory Reports")
})

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

The route /api/management/reports/sales would have the Logger, CORS, and JWT middleware applied in that order.

Group-Specific Middleware Application

You can also add middleware to a group after creating it:

go
// Create a group
adminGroup := e.Group("/admin")

// Add middleware to the group
adminGroup.Use(middleware.Logger())
adminGroup.Use(middleware.BasicAuth(customAuthFunc))

// Add routes
adminGroup.GET("/stats", statsHandler)

Real-World Application: User Authentication

Here's a practical example showing how to use middleware groups for authentication in a web application:

go
package main

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

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

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

// Public routes
e.GET("/", homeHandler)
e.GET("/about", aboutHandler)
e.POST("/login", loginHandler)

// User routes - require authentication
user := e.Group("/user", requireAuth)
user.GET("/profile", profileHandler)
user.PUT("/profile", updateProfileHandler)

// Admin routes - require admin authentication
admin := e.Group("/admin", requireAdminAuth)
admin.GET("/dashboard", adminDashboardHandler)
admin.GET("/users", listUsersHandler)

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

// Custom middleware for user authentication
func requireAuth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Check for auth token
token := c.Request().Header.Get("Authorization")
if token == "" {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Authentication required",
})
}

// Validate token (simplified for example)
if token != "valid-user-token" {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Invalid authentication token",
})
}

return next(c)
}
}

// Custom middleware for admin authentication
func requireAdminAuth(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Check for admin token
token := c.Request().Header.Get("Authorization")
if token != "valid-admin-token" {
return c.JSON(http.StatusForbidden, map[string]string{
"error": "Admin privileges required",
})
}

return next(c)
}
}

// Handler functions
func homeHandler(c echo.Context) error {
return c.String(http.StatusOK, "Welcome to our site!")
}

func aboutHandler(c echo.Context) error {
return c.String(http.StatusOK, "About us page")
}

func loginHandler(c echo.Context) error {
// Login logic would go here
return c.String(http.StatusOK, "Login successful")
}

func profileHandler(c echo.Context) error {
return c.String(http.StatusOK, "User profile page")
}

func updateProfileHandler(c echo.Context) error {
return c.String(http.StatusOK, "Profile updated")
}

func adminDashboardHandler(c echo.Context) error {
return c.String(http.StatusOK, "Admin Dashboard")
}

func listUsersHandler(c echo.Context) error {
return c.String(http.StatusOK, "User list")
}

This example demonstrates how to:

  1. Create public routes accessible to everyone
  2. Create authenticated user routes protected by user authentication middleware
  3. Create admin routes with stricter admin authentication middleware

Best Practices for Middleware Groups

When working with middleware groups in Echo, follow these best practices:

  1. Organize by functionality: Group routes that share similar functionality or access requirements.
  2. Keep middleware specific: Design middleware to do one thing and do it well.
  3. Order matters: Place middleware in the order you want them to execute. For example, put logging middleware first if you want to log every request, even if it fails later middleware.
  4. Use meaningful prefixes: Choose group URL prefixes that make sense for the routes they contain.
  5. Consider nesting carefully: Don't overuse nesting as it can make your API structure complex and hard to follow.

Summary

Echo Middleware Groups provide a powerful way to organize your web application's routes and middleware. By grouping related routes together and applying middleware to those groups, you can create more maintainable, secure, and logically structured applications.

Key takeaways:

  • Groups allow you to apply middleware to multiple routes at once
  • Groups can be nested to create hierarchical structures
  • You can add middleware when creating a group or later with the Use() method
  • Well-organized groups make your code more readable and maintainable
  • Middleware groups are ideal for API versioning and access control

Additional Resources and Exercises

Resources

Exercises

  1. Basic API Structure: Create an Echo application with three groups: /public, /api, and /admin. Apply different middleware to each group.

  2. API Versioning: Build an API with two versions (v1 and v2) using middleware groups. Make v2 require authentication while v1 remains public.

  3. Multi-tenant Application: Design a route structure for a multi-tenant application where each tenant has their own set of routes with tenant-specific middleware.

  4. Middleware Chain: Create a complex middleware chain using groups where requests pass through logging, authentication, rate limiting, and finally a request validator before reaching the handler.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)