Skip to main content

Gin Default Middleware

Introduction

When building web applications with Go's Gin framework, middleware plays a crucial role in processing HTTP requests and responses. Middleware functions are components that sit between the client request and the final route handler, allowing you to process, modify, or intercept requests and responses.

Gin comes with several built-in middleware components that are ready to use out of the box. These default middleware functions provide essential functionality like logging, error recovery, and request parsing. Understanding these built-in middleware components is vital for building robust web applications with Gin.

In this tutorial, we'll explore the default middleware that Gin provides, how they work, and how to configure them for your specific needs.

What is Default Middleware in Gin?

Default middleware in Gin refers to the pre-configured middleware functions that are automatically included when you create a new Gin engine using gin.Default(). These middleware functions handle common tasks like logging requests, recovering from panics, and providing essential web server capabilities.

The Default Middleware Stack

When you initialize a Gin router using gin.Default(), two middleware are automatically included:

  1. Logger middleware - Logs request details like HTTP method, path, status code, and execution time
  2. Recovery middleware - Recovers from any panics that occur during request handling and returns a 500 error

Let's see how this compares to a basic Gin instance:

go
// Creating a Gin router with default middleware
router := gin.Default()

// Equivalent to:
router := gin.New()
router.Use(gin.Logger())
router.Use(gin.Recovery())

1. Logger Middleware

The Logger middleware logs information about incoming requests, including:

  • Time of request
  • HTTP method
  • Request path
  • HTTP status code
  • Request execution time
  • Client IP address

Basic Example

Here's how the Logger middleware works in a simple application:

go
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
// Default router includes Logger middleware
router := gin.Default()

router.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})

router.Run(":8080")
}

Output

When you run this application and make a request to /ping, you'll see log output similar to:

[GIN] 2023/10/20 - 12:34:56 | 200 |      4.52µs |    127.0.0.1 | GET      "/ping"

Customizing Logger Middleware

You can customize the Logger middleware behavior by using gin.LoggerWithConfig:

go
router := gin.New()

// Customized logger
router.Use(gin.LoggerWithConfig(gin.LoggerConfig{
SkipPaths: []string{"/health"}, // Skip logging for health check endpoints
Formatter: func(param gin.LogFormatterParams) string {
// Custom format for log output
return fmt.Sprintf("[GIN] %s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
param.ClientIP,
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
},
}))

// Still need to add Recovery middleware separately
router.Use(gin.Recovery())

2. Recovery Middleware

The Recovery middleware catches any panics that occur during request processing, preventing your server from crashing. When a panic occurs, this middleware:

  1. Recovers from the panic
  2. Returns a 500 Internal Server Error status code to the client
  3. Logs the error and stack trace

Basic Example

Here's an example demonstrating how the Recovery middleware works by handling a route that would otherwise cause a server crash:

go
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
router := gin.Default() // Includes Recovery middleware

router.GET("/panic", func(c *gin.Context) {
// This will cause a panic
var slice []string
slice[999] = "This will panic!" // Index out of range

// Code below won't be reached due to panic
c.JSON(http.StatusOK, gin.H{
"message": "You won't see this",
})
})

router.Run(":8080")
}

Output

When a client accesses the /panic endpoint, instead of the server crashing, they will receive a 500 status response, and the server will log something like:

[GIN] 2023/10/20 - 12:34:56 | 500 |      1.23ms |    127.0.0.1 | GET      "/panic"
[GIN-debug] [Recovery] panic recovered: runtime error: index out of range [999] with length 0
[GIN-debug] [Recovery] /app/main.go:13 (0x55c102)
[GIN-debug] [Recovery] /usr/local/go/src/runtime/panic.go:88 (0x41d10a)
...stack trace continues...

Customizing Recovery Middleware

You can customize the Recovery middleware with gin.RecoveryWithWriter:

go
router := gin.New()
router.Use(gin.Logger())

// Custom recovery middleware
router.Use(gin.RecoveryWithWriter(gin.DefaultErrorWriter, func(c *gin.Context, recovered interface{}) {
if err, ok := recovered.(string); ok {
c.String(http.StatusInternalServerError, fmt.Sprintf("Error: %s", err))
}
c.AbortWithStatus(http.StatusInternalServerError)
}))

Creating a Router Without Default Middleware

There are cases when you may want to create a Gin router without the default middleware, such as:

  1. When you want to use different logging middleware
  2. When you need to customize the middleware stack completely
  3. For performance optimization in specific scenarios

To create a Gin router without default middleware:

go
// Create a router without any middleware
router := gin.New()

// Selectively add middleware
if needsLogging {
router.Use(gin.Logger())
}

// Add custom middleware
router.Use(YourCustomMiddleware())

// Add recovery if needed
router.Use(gin.Recovery())

Real-World Application: API Server with Custom Middleware Configuration

Let's look at a more complete example showing how to configure default middleware for a production API server:

go
package main

import (
"github.com/gin-gonic/gin"
"io"
"log"
"net/http"
"os"
"time"
)

func main() {
// Set Gin to release mode in production
gin.SetMode(gin.ReleaseMode)

// Create a log file
logFile, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(logFile, os.Stdout)

// Create router without default middleware
router := gin.New()

// Configure custom logging
router.Use(gin.LoggerWithConfig(gin.LoggerConfig{
SkipPaths: []string{"/health", "/metrics"},
Formatter: func(param gin.LogFormatterParams) string {
return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
param.TimeStamp.Format(time.RFC1123),
param.ClientIP,
param.Method,
param.Path,
param.Request.Proto,
param.StatusCode,
param.Latency,
param.Request.UserAgent(),
param.ErrorMessage,
)
},
}))

// Add recovery middleware
router.Use(gin.Recovery())

// Add custom middleware for request ID tracking
router.Use(func(c *gin.Context) {
requestID := c.GetHeader("X-Request-ID")
if requestID == "" {
requestID = generateRequestID() // Implement this function to generate unique IDs
}
c.Set("RequestID", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
})

// Add routes
router.GET("/api/v1/users", getUsers)
router.GET("/health", healthCheck)

// Start server
if err := router.Run(":8080"); err != nil {
log.Fatal("Failed to start server: ", err)
}
}

func getUsers(c *gin.Context) {
// API handler logic
c.JSON(http.StatusOK, gin.H{"users": []string{"user1", "user2"}})
}

func healthCheck(c *gin.Context) {
c.Status(http.StatusOK)
}

func generateRequestID() string {
return time.Now().Format("20060102150405") + "-" + fmt.Sprintf("%d", time.Now().UnixNano()%1000000)
}

Common Patterns and Best Practices

  1. Using Conditional Middleware: Apply middleware conditionally based on environment:
go
if gin.Mode() == gin.DebugMode {
router.Use(gin.Logger())
}
  1. Custom Error Handling: Enhance the Recovery middleware with custom error responses:
go
router.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
if err, ok := recovered.(error); ok {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"message": err.Error(),
})
}
c.AbortWithStatus(http.StatusInternalServerError)
}))
  1. Selective Application: Apply middleware to specific routes or route groups:
go
// Apply middleware to a group
adminGroup := router.Group("/admin", AdminAuthMiddleware())
{
adminGroup.GET("/stats", getStats)
}

// No special middleware for public routes
router.GET("/public", publicHandler)

Summary

The Gin framework provides powerful default middleware components - Logger and Recovery - that form the foundation of most Gin applications. These middleware functions handle essential tasks like request logging and error recovery, making your applications more robust with minimal configuration.

Key takeaways:

  1. gin.Default() automatically includes Logger and Recovery middleware
  2. The Logger middleware provides detailed request logs
  3. The Recovery middleware prevents server crashes by capturing panics
  4. You can create a custom middleware stack with gin.New() and selectively add middleware
  5. Both default middleware can be customized to suit your application's needs

Understanding how to configure and optimize these default middleware components allows you to build web applications that are both performant and maintainable.

Additional Resources

Exercises

  1. Create a custom logger middleware that filters out specific paths like /health and /metrics
  2. Implement a custom recovery handler that formats error responses as JSON
  3. Build a middleware stack that combines the default middleware with rate limiting and CORS support
  4. Create a middleware that adds security headers to all responses
  5. Implement a middleware that logs request bodies for debugging (but only in development mode)


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)