middleware

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 27, 2025 License: MIT Imports: 7 Imported by: 0

README

middleware Package

HTTP middleware for Gin framework with logging, recovery, CORS, rate limiting, and more.

Features

  • Request ID: Unique ID for request tracing
  • Logging: Automatic request/response logging
  • Recovery: Panic recovery with error logging
  • CORS: Cross-Origin Resource Sharing configuration
  • Rate Limiting: IP-based rate limiting
  • Error Handling: Standardized error responses
  • Timeout: Request timeout enforcement

Installation

import "github.com/LaRestoOU/laresto-go-common/pkg/middleware"

Quick Start

router := gin.New()

// Add middleware
router.Use(middleware.RequestID())
router.Use(middleware.Logger(log))
router.Use(middleware.Recovery(log))
router.Use(middleware.CORS([]string{"https://app.laresto.com"}))
router.Use(middleware.ErrorHandler())

router.GET("/users", GetUsers)

Middleware Functions

RequestID

Generates unique request ID for tracing.

router.Use(middleware.RequestID())

Features:

  • Generates UUID if not present
  • Uses existing X-Request-ID header if provided
  • Adds to context as request_id
  • Returns in response header

Usage in handlers:

func Handler(c *gin.Context) {
    requestID, _ := c.Get("request_id")
    log.Info("Processing request", "request_id", requestID)
}
Logger

Logs HTTP requests with duration and status.

log := logger.New(logger.Config{...})
router.Use(middleware.Logger(log))

Logs:

  • HTTP method
  • Request path
  • Status code
  • Duration (milliseconds)
  • Request ID
  • Client IP

Example log:

{
  "level": "info",
  "method": "GET",
  "path": "/users/123",
  "status": 200,
  "duration_ms": 45,
  "request_id": "abc-123",
  "ip": "192.168.1.1",
  "message": "HTTP request"
}
Recovery

Recovers from panics and returns 500 error.

log := logger.New(logger.Config{...})
router.Use(middleware.Recovery(log))

Prevents crashes:

router.GET("/panic", func(c *gin.Context) {
    panic("something went wrong")
    // Returns 500 instead of crashing
})
CORS

Handles Cross-Origin Resource Sharing.

// Allow specific origins
router.Use(middleware.CORS([]string{
    "https://app.laresto.com",
    "https://admin.laresto.com",
}))

// Allow all origins (development only!)
router.Use(middleware.CORS([]string{"*"}))

Headers set:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers
  • Access-Control-Allow-Credentials
  • Access-Control-Max-Age

Handles preflight:

OPTIONS /users -> 204 No Content
RateLimiter

IP-based rate limiting.

// 100 requests per minute per IP
limiter := middleware.NewRateLimiter(100, 1*time.Minute)
router.Use(limiter.Middleware())

Returns 429 when exceeded:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Max 100 requests per 1m0s"
  }
}
ErrorHandler

Converts errors to JSON responses.

router.Use(middleware.ErrorHandler())

router.GET("/users/:id", func(c *gin.Context) {
    user, err := service.GetUser(id)
    if err != nil {
        c.Error(err) // ErrorHandler converts to JSON
        return
    }
    c.JSON(200, user)
})

Response format:

{
  "error": {
    "code": "NOT_FOUND",
    "message": "User not found",
    "details": {
      "user_id": "123"
    }
  }
}
Timeout

Enforces request timeout.

// 30 second timeout
router.Use(middleware.Timeout(30 * time.Second))

Returns 504 on timeout:

{
  "error": {
    "code": "REQUEST_TIMEOUT",
    "message": "Request timeout exceeded"
  }
}

Complete Setup Example

package main

import (
    "time"
    
    "github.com/LaRestoOU/laresto-go-common/pkg/logger"
    "github.com/LaRestoOU/laresto-go-common/pkg/middleware"
    "github.com/gin-gonic/gin"
)

func main() {
    // Create logger
    log := logger.New(logger.Config{
        Level:       "info",
        ServiceName: "auth-service",
        Environment: "production",
    })
    
    // Create router
    router := gin.New()
    
    // Add middleware (order matters!)
    router.Use(middleware.RequestID())           // 1. Generate request ID
    router.Use(middleware.Logger(log))           // 2. Log requests
    router.Use(middleware.Recovery(log))         // 3. Recover from panics
    router.Use(middleware.CORS([]string{         // 4. Handle CORS
        "https://app.laresto.com",
    }))
    
    // Rate limiting
    limiter := middleware.NewRateLimiter(100, 1*time.Minute)
    router.Use(limiter.Middleware())             // 5. Rate limit
    
    router.Use(middleware.Timeout(30*time.Second)) // 6. Timeout
    router.Use(middleware.ErrorHandler())        // 7. Error handling (last!)
    
    // Routes
    router.GET("/health", HealthCheck)
    router.POST("/login", Login)
    router.GET("/users/:id", GetUser)
    
    router.Run(":8080")
}

Middleware Order

Critical: Middleware order matters!

// ✅ CORRECT ORDER
router.Use(middleware.RequestID())      // First - needed by others
router.Use(middleware.Logger(log))      // Early - log everything
router.Use(middleware.Recovery(log))    // Early - catch all panics
router.Use(middleware.CORS(...))        // Before routes
router.Use(limiter.Middleware())        // Before routes
router.Use(middleware.Timeout(...))     // Before routes
router.Use(middleware.ErrorHandler())   // Last - handle all errors

// ❌ WRONG ORDER
router.Use(middleware.ErrorHandler())   // Too early!
router.Use(middleware.RequestID())      // Should be first

Route Groups

Apply middleware to specific routes:

// Public routes (no auth)
public := router.Group("/")
public.Use(limiter.Middleware())
{
    public.POST("/login", Login)
    public.POST("/register", Register)
}

// Protected routes (with auth)
protected := router.Group("/api")
protected.Use(AuthMiddleware(tokenManager))
protected.Use(strictLimiter.Middleware()) // Stricter limits
{
    protected.GET("/profile", GetProfile)
    protected.PUT("/profile", UpdateProfile)
}

// Admin routes
admin := router.Group("/admin")
admin.Use(AuthMiddleware(tokenManager))
admin.Use(RequireRole("admin"))
{
    admin.GET("/users", ListUsers)
    admin.DELETE("/users/:id", DeleteUser)
}

Custom Middleware

Create your own middleware:

func CustomMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // Before request
        start := time.Now()
        
        // Process request
        c.Next()
        
        // After request
        duration := time.Since(start)
        log.Info("Custom metric", "duration", duration)
    }
}

router.Use(CustomMiddleware())

Best Practices

DO ✅
// Use RequestID first
router.Use(middleware.RequestID())

// Log all requests
router.Use(middleware.Logger(log))

// Always recover from panics
router.Use(middleware.Recovery(log))

// Set appropriate rate limits
limiter := middleware.NewRateLimiter(100, 1*time.Minute)

// Use ErrorHandler last
router.Use(middleware.ErrorHandler())
DON'T ❌
// Don't allow all origins in production
router.Use(middleware.CORS([]string{"*"})) // Development only!

// Don't set limits too low
limiter := middleware.NewRateLimiter(1, 1*time.Minute) // Too strict!

// Don't forget error handling
// Always use ErrorHandler()

// Don't skip recovery
// Panics will crash the server!

Testing

func TestMiddleware(t *testing.T) {
    gin.SetMode(gin.TestMode)
    
    router := gin.New()
    router.Use(middleware.RequestID())
    router.Use(middleware.Logger(logger.NewDefault()))
    
    router.GET("/test", func(c *gin.Context) {
        c.String(200, "OK")
    })
    
    req := httptest.NewRequest("GET", "/test", nil)
    resp := httptest.NewRecorder()
    
    router.ServeHTTP(resp, req)
    
    assert.Equal(t, 200, resp.Code)
    assert.NotEmpty(t, resp.Header().Get("X-Request-ID"))
}

Performance Tips

  1. Use appropriate rate limits: Balance protection vs usability
  2. Set reasonable timeouts: 30s for API requests
  3. Minimize middleware count: Only use what you need
  4. Order matters: Put expensive middleware last

Security

// ✅ Production CORS
router.Use(middleware.CORS([]string{
    "https://app.laresto.com",
    "https://admin.laresto.com",
}))

// ✅ Strict rate limiting for sensitive endpoints
strictLimiter := middleware.NewRateLimiter(10, 1*time.Minute)
router.POST("/login", strictLimiter.Middleware(), Login)

// ✅ Always recover from panics
router.Use(middleware.Recovery(log))

// ✅ Set timeouts
router.Use(middleware.Timeout(30 * time.Second))

License

MIT License - see LICENSE file for details

Documentation

Overview

Package middleware provides HTTP middleware for Gin framework.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CORS

func CORS(allowOrigins []string) gin.HandlerFunc

CORS handles Cross-Origin Resource Sharing.

func ErrorHandler

func ErrorHandler() gin.HandlerFunc

ErrorHandler converts errors to JSON responses.

func Logger

func Logger(log *logger.Logger) gin.HandlerFunc

Logger logs HTTP requests with duration and status.

func Recovery

func Recovery(log *logger.Logger) gin.HandlerFunc

Recovery recovers from panics and returns 500 error.

func RequestID

func RequestID() gin.HandlerFunc

RequestID adds a unique request ID to each request.

func Timeout

func Timeout(timeout time.Duration) gin.HandlerFunc

Timeout adds a timeout to the request context.

Types

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter implements simple rate limiting.

func NewRateLimiter

func NewRateLimiter(limit int, window time.Duration) *RateLimiter

NewRateLimiter creates a new rate limiter.

func (*RateLimiter) Middleware

func (rl *RateLimiter) Middleware() gin.HandlerFunc

Middleware returns the rate limiting middleware.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL