Middleware Guide

Learn how to use middleware for cross-cutting concerns like authentication, logging, CORS, and more in your Squirrel applications.

AuthenticationLoggingCORS
What is Middleware?
Understanding the middleware pattern in web applications

Middleware is a function that sits between the incoming request and the outgoing response. It can modify the request, response, or both, and decide whether to pass control to the next middleware in the chain.

Middleware Flow:

Request → Middleware 1 → Middleware 2 → Handler → Response
Each middleware can modify the request/response or stop the chain
Creating Custom Middleware
Build your own middleware functions
package main

import (
    "fmt"
    "log"
    "net/http"
    "time"
    "github.com/squirrel-land/squirrel"
)

// Logging middleware
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        // Call the next handler
        next.ServeHTTP(w, r)
        
        // Log after the request is processed
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// Authentication middleware
func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        token := r.Header.Get("Authorization")
        
        if token == "" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }
        
        // Validate token (simplified)
        if token != "Bearer valid-token" {
            http.Error(w, "Invalid token", http.StatusUnauthorized)
            return
        }
        
        // Token is valid, continue to next handler
        next.ServeHTTP(w, r)
    })
}

func main() {
    mux := squirrel.NewSqurlMux()
    
    // Apply middleware globally
    mux.Use(loggingMiddleware)
    
    // Public routes
    mux.HandleFunc("/", homeHandler)
    mux.HandleFunc("/login", loginHandler)
    
    // Protected routes
    protected := mux.Group("/api")
    protected.Use(authMiddleware)
    protected.Get("/profile", profileHandler)
    protected.Get("/data", dataHandler)
    
    http.ListenAndServe(":8080", mux)
}
Built-in Middleware
Use Squirrels built-in middleware for common tasks
package main

import (
    "net/http"
    "github.com/squirrel-land/squirrel"
    "github.com/squirrel-land/squirrel/middleware"
)

func main() {
    mux := squirrel.NewSqurlMux()
    
    // CORS middleware with configuration
    corsConfig := middleware.CORSConfig{
        AllowOrigins:     []string{"https://example.com", "https://app.example.com"},
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
        AllowHeaders:     []string{"Content-Type", "Authorization"},
        AllowCredentials: true,
        MaxAge:           3600,
    }
    
    mux.Use(middleware.CORS(corsConfig))
    
    // Your routes
    mux.Get("/api/users", getUsersHandler)
    mux.Post("/api/users", createUserHandler)
    
    http.ListenAndServe(":8080", mux)
}
Middleware Chain Order
Understanding the order of middleware execution
func main() {
    mux := squirrel.NewSqurlMux()
    
    // Middleware execution order:
    mux.Use(middleware.Recovery())      // 1. Catch panics first
    mux.Use(middleware.Logger())        // 2. Log all requests
    mux.Use(middleware.CORS())          // 3. Handle CORS
    mux.Use(middleware.RateLimit())     // 4. Rate limiting
    mux.Use(middleware.Auth())          // 5. Authentication last
    
    // Request flow:
    // Request → Recovery → Logger → CORS → RateLimit → Auth → Handler
    // Response ← Recovery ← Logger ← CORS ← RateLimit ← Auth ← Handler
    
    mux.HandleFunc("/api/data", dataHandler)
    http.ListenAndServe(":8080", mux)
}

Recommended Order:

  1. Recovery (catch panics)
  2. Logging (log all requests)
  3. CORS (handle preflight requests)
  4. Compression (compress responses)
  5. Rate limiting (prevent abuse)
  6. Authentication (verify users)
  7. Authorization (check permissions)
Conditional Middleware
Apply middleware conditionally based on routes or conditions
package main

import (
    "net/http"
    "strings"
    "github.com/squirrel-land/squirrel"
)

// Conditional middleware wrapper
func conditionalMiddleware(condition func(*http.Request) bool, middleware squirrel.Middleware) squirrel.Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if condition(r) {
                middleware(next).ServeHTTP(w, r)
            } else {
                next.ServeHTTP(w, r)
            }
        })
    }
}

func main() {
    mux := squirrel.NewSqurlMux()
    
    // Apply auth middleware only to API routes
    apiOnlyAuth := conditionalMiddleware(
        func(r *http.Request) bool {
            return strings.HasPrefix(r.URL.Path, "/api/")
        },
        authMiddleware,
    )
    
    mux.Use(apiOnlyAuth)
    
    // Public routes (no auth required)
    mux.HandleFunc("/", homeHandler)
    mux.HandleFunc("/about", aboutHandler)
    
    // API routes (auth required)
    mux.HandleFunc("/api/users", usersHandler)
    mux.HandleFunc("/api/posts", postsHandler)
    
    http.ListenAndServe(":8080", mux)
}

// Skip middleware for specific paths
func skipPaths(paths []string, middleware squirrel.Middleware) squirrel.Middleware {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            for _, path := range paths {
                if r.URL.Path == path {
                    next.ServeHTTP(w, r)
                    return
                }
            }
            middleware(next).ServeHTTP(w, r)
        })
    }
}

// Usage: Skip auth for login and register endpoints
// mux.Use(skipPaths([]string{"/login", "/register"}, authMiddleware))
Best Practices
Tips for effective middleware usage

Keep middleware focused

Each middleware should have a single responsibility (logging, auth, etc.)

Handle errors gracefully

Always provide meaningful error responses and dont let panics crash your server

Use context for request-scoped data

Store user information, request IDs, etc. in the request context

Test middleware independently

Write unit tests for your middleware functions to ensure they work correctly