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"))
}
- Use appropriate rate limits: Balance protection vs usability
- Set reasonable timeouts: 30s for API requests
- Minimize middleware count: Only use what you need
- 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