Echo Middleware Introduction
Middleware is a powerful concept in web application development that allows you to process requests and responses in a modular, layered way. In the Echo framework, middleware provides a flexible mechanism to add functionality to your application without cluttering your route handlers.
What is Middleware?
At its core, middleware is a function that has access to:
- The request object
- The response object
- The next middleware function in the application's request-response cycle
Middleware functions can:
- Execute any code
- Modify request and response objects
- End the request-response cycle
- Call the next middleware in the stack
Understanding Echo Middleware Flow
In Echo, middleware follows a "pipeline" pattern where each request flows through a series of middleware functions before reaching the route handler and then back through the middleware stack in reverse order.
Client Request → Middleware 1 → Middleware 2 → Route Handler → Middleware 2 → Middleware 1 → Client Response
This enables powerful patterns like pre-processing requests and post-processing responses.
Basic Middleware Syntax
In Echo, a middleware function has the following signature:
func(echo.HandlerFunc) echo.HandlerFunc
Here's a simple example of a custom middleware:
func LoggerMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Pre-processing (before request handling)
fmt.Println("Request received:", c.Request().Method, c.Request().URL.Path)
// Call the next handler in the chain
err := next(c)
// Post-processing (after request handling)
fmt.Println("Response status:", c.Response().Status)
return err
}
}
Using Middleware in Echo
There are multiple ways to apply middleware in an Echo application:
Global Middleware
Global middleware is executed for every request to your application:
// Create a new Echo instance
e := echo.New()
// Apply middleware globally
e.Use(middleware.Logger())
e.Use(middleware.Recover())
Group-Level Middleware
You can also apply middleware to a group of routes:
// Create a group
adminGroup := e.Group("/admin")
// Apply middleware to the group
adminGroup.Use(middleware.BasicAuth(validateAdminCredentials))
// Define routes for the group
adminGroup.GET("/dashboard", adminDashboardHandler)
Route-Level Middleware
For the finest control, you can apply middleware to specific routes:
e.GET("/protected", protectedHandler, middleware.JWT([]byte("secret")))
Built-in Echo Middleware
Echo comes with several useful middleware components out of the box:
- Logger: Logs HTTP requests with method, path, status code, and latency
- Recover: Recovers from panics anywhere in the middleware chain
- BasicAuth: Provides HTTP basic authentication
- JWT: JSON Web Token authentication
- CORS: Enables Cross-Origin Resource Sharing
- Secure: Adds security headers
- Gzip: Compresses responses
Practical Example: Creating and Using Custom Middleware
Let's create a simple middleware that tracks the time taken to process each request:
package main
import (
"fmt"
"net/http"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
// RequestTimer middleware records the time taken to process requests
func RequestTimer(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Start timer
start := time.Now()
// Process request
err := next(c)
// Calculate duration
duration := time.Since(start)
// Log the time taken
fmt.Printf("Request to %s took %v\n", c.Request().URL.Path, duration)
return err
}
}
func main() {
// Create a new Echo instance
e := echo.New()
// Apply built-in middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Apply our custom middleware
e.Use(RequestTimer)
// Routes
e.GET("/", func(c echo.Context) error {
// Simulate work
time.Sleep(100 * time.Millisecond)
return c.String(http.StatusOK, "Hello, World!")
})
e.GET("/slow", func(c echo.Context) error {
// Simulate slower work
time.Sleep(500 * time.Millisecond)
return c.String(http.StatusOK, "This was a slow response")
})
// Start server
e.Logger.Fatal(e.Start(":8080"))
}
Output:
When you run this application and make requests, you'll see output like:
Request to / took 103.532ms
Request to /slow took 502.814ms
Real-World Middleware Examples
Authentication Middleware
func JWTAuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
token := c.Request().Header.Get("Authorization")
if token == "" {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Missing authentication token",
})
}
// Validate token (simplified for example)
if !isValidToken(token) {
return c.JSON(http.StatusUnauthorized, map[string]string{
"error": "Invalid authentication token",
})
}
// Token is valid, set user in context
userID := getUserIDFromToken(token)
c.Set("user_id", userID)
return next(c)
}
}
Request Rate Limiting
func RateLimiter(requestsPerMinute int) echo.MiddlewareFunc {
// Map to store client IPs and their request counts
clients := make(map[string]*ClientTracker)
var mu sync.Mutex
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
ip := c.RealIP()
mu.Lock()
if _, exists := clients[ip]; !exists {
clients[ip] = &ClientTracker{
count: 0,
lastReset: time.Now(),
}
}
client := clients[ip]
// Reset counter if a minute has passed
if time.Since(client.lastReset) > time.Minute {
client.count = 0
client.lastReset = time.Now()
}
if client.count >= requestsPerMinute {
mu.Unlock()
return c.JSON(http.StatusTooManyRequests, map[string]string{
"error": "Rate limit exceeded",
})
}
client.count++
mu.Unlock()
return next(c)
}
}
}
type ClientTracker struct {
count int
lastReset time.Time
}
Middleware Chain Order
The order in which you apply middleware matters! Consider this example:
e := echo.New()
// Order matters!
e.Use(middleware.Logger()) // 1. Log all requests
e.Use(middleware.Recover()) // 2. Recover from panics
e.Use(AuthMiddleware()) // 3. Authenticate requests
In this case, even if authentication fails, the request will be logged and the server will be protected from panics. If you placed the AuthMiddleware
first, unauthenticated requests wouldn't be logged.
Summary
Echo middleware provides a clean, powerful way to add cross-cutting concerns to your web application. You've learned:
- What middleware is and how it works in Echo
- How to create custom middleware
- Different ways to apply middleware (global, group, route)
- The importance of middleware order
- Real-world middleware examples
By mastering Echo middleware, you can keep your route handlers clean and focused on their primary responsibilities while handling common concerns like logging, authentication, and error handling in a modular way.
Additional Resources
Exercises
- Create a middleware that logs the user agent of each request
- Implement a middleware that checks if a site is in maintenance mode
- Build a middleware that adds custom headers to all responses
- Create a middleware that measures and logs memory usage for each request
- Implement role-based authorization middleware that works with the authentication middleware
By working through these exercises, you'll gain practical experience with Echo middleware and strengthen your understanding of the concept.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)