Skip to main content

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:

go
func(echo.HandlerFunc) echo.HandlerFunc

Here's a simple example of a custom middleware:

go
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:

go
// 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:

go
// 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:

go
e.GET("/protected", protectedHandler, middleware.JWT([]byte("secret")))

Built-in Echo Middleware

Echo comes with several useful middleware components out of the box:

  1. Logger: Logs HTTP requests with method, path, status code, and latency
  2. Recover: Recovers from panics anywhere in the middleware chain
  3. BasicAuth: Provides HTTP basic authentication
  4. JWT: JSON Web Token authentication
  5. CORS: Enables Cross-Origin Resource Sharing
  6. Secure: Adds security headers
  7. 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:

go
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

go
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

go
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:

go
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

  1. Create a middleware that logs the user agent of each request
  2. Implement a middleware that checks if a site is in maintenance mode
  3. Build a middleware that adds custom headers to all responses
  4. Create a middleware that measures and logs memory usage for each request
  5. 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! :)