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
| Level | Code | Scope |
|---|---|---|
| Global | r.Use(mw) | All routes |
| Group | g := r.Group("/api"); g.Use(mw) | Group routes |
| Route | r.GET("/path", mw, handler) | Single route |
| Inline | r.GET("/path", func(c){c.Next()}) | Single route inline |