Gin Middleware Guide

Gin middleware patterns: built-in Recovery and Logger, CORS setup, JWT authentication middleware, rate limiting, and context data passing.

1. Built-in Middleware

package main

import (
    "github.com/gin-gonic/gin"
    "log"
    "time"
)

func main() {
    // gin.Default() = gin.New() + Logger + Recovery
    r := gin.Default()

    // Or compose manually:
    r = gin.New()
    r.Use(gin.Logger())    // request logging
    r.Use(gin.Recovery())  // recover from panics

    // Custom logger format
    r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
        return fmt.Sprintf("[%s] %s %s %d %s %s\n",
            param.TimeStamp.Format(time.RFC3339),
            param.Method,
            param.Path,
            param.StatusCode,
            param.Latency,
            param.ErrorMessage,
        )
    }))

    r.Run(":8080")
}

2. Custom Middleware

// Request ID middleware
func RequestID() gin.HandlerFunc {
    return func(c *gin.Context) {
        id := c.GetHeader("X-Request-ID")
        if id == "" {
            id = uuid.New().String()
        }
        c.Set("requestID", id)
        c.Header("X-Request-ID", id)
        c.Next() // call next handler
    }
}

// Timeout middleware
func Timeout(duration time.Duration) gin.HandlerFunc {
    return func(c *gin.Context) {
        ctx, cancel := context.WithTimeout(c.Request.Context(), duration)
        defer cancel()
        c.Request = c.Request.WithContext(ctx)

        done := make(chan struct{})
        go func() {
            c.Next()
            close(done)
        }()

        select {
        case <-done:
        case <-ctx.Done():
            c.AbortWithStatusJSON(http.StatusRequestTimeout, gin.H{"error": "Request timeout"})
        }
    }
}

// Usage
r.Use(RequestID())
r.Use(Timeout(10 * time.Second))

3. JWT Authentication Middleware

func JWTAuth(secret string) gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
            return
        }

        tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
        claims := &Claims{}
        token, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) {
            if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, fmt.Errorf("unexpected signing method")
            }
            return []byte(secret), nil
        })

        if err != nil || !token.Valid {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            return
        }

        // Store claims in context
        c.Set("userID", claims.UserID)
        c.Set("userRole", claims.Role)
        c.Next()
    }
}

// Retrieve from context
func GetArticle(c *gin.Context) {
    userID, _ := c.Get("userID")
    role, _ := c.GetString("userRole")
    _ = userID
    _ = role
}

4. CORS Middleware

import "github.com/gin-contrib/cors"

// Simple
r.Use(cors.Default())

// Custom
r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://app.example.com"},
    AllowMethods:     []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
    AllowHeaders:     []string{"Authorization", "Content-Type", "X-Request-ID"},
    ExposeHeaders:    []string{"X-Total-Count"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
    AllowOriginFunc: func(origin string) bool {
        return strings.HasSuffix(origin, ".example.com")
    },
}))

5. Rate Limiting Middleware

import "golang.org/x/time/rate"

// Simple in-memory rate limiter
var limiter = rate.NewLimiter(rate.Every(time.Second), 10) // 10 req/sec

func RateLimit() gin.HandlerFunc {
    return func(c *gin.Context) {
        if !limiter.Allow() {
            c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
                "error": "Rate limit exceeded",
            })
            return
        }
        c.Next()
    }
}

// Per-IP limiter
type IPRateLimiter struct {
    limiters sync.Map
    r        rate.Limit
    b        int
}

func (l *IPRateLimiter) getLimiter(ip string) *rate.Limiter {
    val, _ := l.limiters.LoadOrStore(ip, rate.NewLimiter(l.r, l.b))
    return val.(*rate.Limiter)
}

6. Middleware Application Levels

LevelCodeScope
Globalr.Use(mw)All routes
Groupg := r.Group("/api"); g.Use(mw)Group routes
Router.GET("/path", mw, handler)Single route
Inliner.GET("/path", func(c){c.Next()})Single route inline