Middleware
Overview
Middleware provides a way to filter and modify HTTP requests entering your application. The framework includes built-in middleware and supports custom middleware creation.
Built-in Middleware
Authentication Middleware
// JWT Authentication
func JWTAuthMiddleware(secretKey string) gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.JSON(401, gin.H{"error": "Missing authorization token"})
c.Abort()
return
}
// Remove "Bearer " prefix
if strings.HasPrefix(tokenString, "Bearer ") {
tokenString = tokenString[7:]
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(secretKey), nil
})
if err != nil || !token.Valid {
c.JSON(401, gin.H{"error": "Invalid token"})
c.Abort()
return
}
// Store user info in context
if claims, ok := token.Claims.(jwt.MapClaims); ok {
c.Set("user_id", claims["user_id"])
c.Set("user_email", claims["email"])
}
c.Next()
}
}
CORS Middleware
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Header("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
Logging Middleware
func LoggingMiddleware(logger *log.Logger) gin.HandlerFunc {
return gin.LoggerWithConfig(gin.LoggerConfig{
Output: logger.Writer(),
Formatter: func(param gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.ClientIP,
param.TimeStamp.Format(time.RFC1123),
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
},
})
}
Rate Limiting Middleware
func RateLimitMiddleware(requests int, window time.Duration) gin.HandlerFunc {
type client struct {
requests int
window time.Time
}
clients := make(map[string]*client)
mutex := &sync.Mutex{}
return func(c *gin.Context) {
ip := c.ClientIP()
mutex.Lock()
defer mutex.Unlock()
now := time.Now()
// Clean up old entries
if client, exists := clients[ip]; exists {
if now.Sub(client.window) > window {
client.requests = 0
client.window = now
}
} else {
clients[ip] = &client{0, now}
}
client := clients[ip]
client.requests++
if client.requests > requests {
c.JSON(429, gin.H{"error": "Rate limit exceeded"})
c.Abort()
return
}
c.Next()
}
}
Custom Middleware
Creating Custom Middleware
func CustomAuthMiddleware(userFetcher *srv.UserFetcher) gin.HandlerFunc {
return func(c *gin.Context) {
userId := c.GetHeader("X-User-ID")
if userId == "" {
c.JSON(401, gin.H{"error": "User ID required"})
c.Abort()
return
}
user, exists, err := userFetcher.FindOneById(c.Request.Context(), userId, nil)
if err != nil {
c.JSON(500, gin.H{"error": "Database error"})
c.Abort()
return
}
if !exists {
c.JSON(404, gin.H{"error": "User not found"})
c.Abort()
return
}
if !user.Active {
c.JSON(403, gin.H{"error": "Account disabled"})
c.Abort()
return
}
// Store user in context
c.Set("current_user", user)
c.Next()
}
}
Validation Middleware
func ValidateJSONMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Header.Get("Content-Type") == "application/json" {
var json map[string]interface{}
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(400, gin.H{"error": "Invalid JSON format"})
c.Abort()
return
}
// Re-bind the JSON data
c.Set("json_data", json)
}
c.Next()
}
}
Error Handling Middleware
func ErrorHandlerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic recovered: %v", err)
c.JSON(500, gin.H{"error": "Internal server error"})
c.Abort()
}
}()
c.Next()
// Handle any errors that were set during request processing
if len(c.Errors) > 0 {
err := c.Errors.Last().Err
switch e := err.(type) {
case *ValidationError:
c.JSON(400, gin.H{"error": e.Message, "field": e.Field})
case *NotFoundError:
c.JSON(404, gin.H{"error": "Resource not found"})
default:
c.JSON(500, gin.H{"error": "Internal server error"})
}
c.Abort()
}
}
}
Middleware Registration
Global Middleware
func SetupRouter(container *container.Container) *gin.Engine {
router := gin.New()
// Global middleware
router.Use(CORSMiddleware())
router.Use(LoggingMiddleware(container.Logger))
router.Use(ErrorHandlerMiddleware())
router.Use(RateLimitMiddleware(100, time.Minute))
// Register routes
RegisterRoutes(router, container)
return router
}
Group Middleware
func RegisterAPIRoutes(router *gin.Engine, container *container.Container) {
// API routes with authentication
api := router.Group("/api/v1")
api.Use(JWTAuthMiddleware(container.Config.JWTSecret))
api.Use(ValidateJSONMiddleware())
{
api.GET("/gardens", container.GardenController.ApiList)
api.POST("/gardens", container.GardenController.ApiCreate)
api.GET("/gardens/:id", container.GardenController.ApiShow)
}
// Public API routes
public := router.Group("/public")
{
public.GET("/health", container.HealthController.Check)
public.POST("/auth/login", container.AuthController.Login)
}
}
Route-Specific Middleware
func RegisterAdminRoutes(router *gin.Engine, container *container.Container) {
admin := router.Group("/admin")
admin.Use(JWTAuthMiddleware(container.Config.JWTSecret))
admin.Use(AdminRequiredMiddleware())
{
// Only admin users can access these routes
admin.GET("/users", container.UserController.AdminList)
admin.DELETE("/users/:id", container.UserController.AdminDelete)
}
}
func AdminRequiredMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
user, exists := c.Get("current_user")
if !exists {
c.JSON(403, gin.H{"error": "Access denied"})
c.Abort()
return
}
if !user.(*mdl.User).IsAdmin {
c.JSON(403, gin.H{"error": "Admin access required"})
c.Abort()
return
}
c.Next()
}
}
Middleware Chain
Understanding Execution Order
router.Use(Middleware1()) // Executes first (before request)
router.Use(Middleware2()) // Executes second (before request)
router.Use(Middleware3()) // Executes third (before request)
router.GET("/endpoint", Handler) // Main handler
// After handler completes:
// Middleware3() continues (after request)
// Middleware2() continues (after request)
// Middleware1() continues (after request)
Conditional Middleware
func ConditionalMiddleware(condition func(*gin.Context) bool, middleware gin.HandlerFunc) gin.HandlerFunc {
return func(c *gin.Context) {
if condition(c) {
middleware(c)
} else {
c.Next()
}
}
}
// Usage
router.Use(ConditionalMiddleware(
func(c *gin.Context) bool {
return strings.HasPrefix(c.Request.URL.Path, "/api/")
},
JWTAuthMiddleware(secretKey),
))
Testing Middleware
Unit Testing
func TestJWTAuthMiddleware(t *testing.T) {
// Setup
router := gin.New()
router.Use(JWTAuthMiddleware("test-secret"))
router.GET("/protected", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "success"})
})
// Test missing token
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/protected", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 401, w.Code)
// Test valid token
token := createTestJWT("test-secret", "user123")
w = httptest.NewRecorder()
req = httptest.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer "+token)
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
}
Integration Testing
func TestMiddlewareChain(t *testing.T) {
container := setupTestContainer()
router := SetupRouter(container)
// Test that all middleware executes correctly
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/api/v1/gardens", nil)
req.Header.Set("Authorization", "Bearer "+validToken)
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
assert.Contains(t, w.Header().Get("Access-Control-Allow-Origin"), "*")
}
Best Practices
Performance Considerations
// Cache expensive operations
func CachedAuthMiddleware(cache *sync.Map) gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
// Check cache first
if userData, exists := cache.Load(token); exists {
c.Set("user", userData)
c.Next()
return
}
// Validate and cache
user := validateToken(token)
cache.Store(token, user)
c.Set("user", user)
c.Next()
}
}
Error Handling
func SafeMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
log.Printf("Middleware panic: %v", r)
if !c.Writer.Written() {
c.JSON(500, gin.H{"error": "Internal server error"})
}
}
}()
c.Next()
}
}
Configuration
type MiddlewareConfig struct {
EnableCORS bool
EnableAuth bool
RateLimit int
JWTSecret string
AllowedOrigins []string
}
func SetupMiddleware(router *gin.Engine, config *MiddlewareConfig) {
if config.EnableCORS {
router.Use(CORSMiddleware(config.AllowedOrigins))
}
if config.RateLimit > 0 {
router.Use(RateLimitMiddleware(config.RateLimit, time.Minute))
}
if config.EnableAuth {
router.Use(JWTAuthMiddleware(config.JWTSecret))
}
}
Middleware provides a powerful way to add cross-cutting concerns to your application while keeping your handlers focused on their core business logic.