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
// 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
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:
- All routes use the Recover middleware
- The
/api
routes use both Recover and Logger middleware - The
/api/admin
routes use Recover, Logger, and BasicAuth middleware
Example 2: Organizing API Versions
Middleware groups are excellent for API versioning:
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:
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:
// 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:
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:
- Create public routes accessible to everyone
- Create authenticated user routes protected by user authentication middleware
- Create admin routes with stricter admin authentication middleware
Best Practices for Middleware Groups
When working with middleware groups in Echo, follow these best practices:
- Organize by functionality: Group routes that share similar functionality or access requirements.
- Keep middleware specific: Design middleware to do one thing and do it well.
- 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.
- Use meaningful prefixes: Choose group URL prefixes that make sense for the routes they contain.
- 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
-
Basic API Structure: Create an Echo application with three groups:
/public
,/api
, and/admin
. Apply different middleware to each group. -
API Versioning: Build an API with two versions (v1 and v2) using middleware groups. Make v2 require authentication while v1 remains public.
-
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.
-
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! :)