Gin Middleware Basics
Introduction
Middleware is a fundamental concept in web development that allows you to process HTTP requests and responses before they reach your application's endpoints or after they leave them. In the context of the Gin framework, middleware functions are functions that have access to the request context, the request object, and the response object. They can execute code, make changes to the request and responses, and terminate the request-response cycle.
This guide will introduce you to the basics of middleware in Gin, how to use built-in middleware, and how to create your own custom middleware.
What is Middleware?
In Gin, middleware is essentially a function that:
- Receives the current request context (
*gin.Context
) - Performs some processing
- Either passes control to the next middleware function in the chain by calling
c.Next()
or terminates the request by callingc.Abort()
Middleware functions are executed in a specific order - the order in which they are added to the router or group.
Using Built-in Middleware
Gin provides several built-in middleware functions that address common web application needs.
Basic Example
Here's a simple example of using Gin's built-in logger and recovery middleware:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
// Create a default Gin router with Logger and Recovery middleware
r := gin.Default()
// Define a route
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
// Run the server
r.Run(":8080")
}
In this example:
gin.Default()
creates a router with the Logger and Recovery middleware already attached.- Logger middleware logs request details
- Recovery middleware recovers from any panics and returns a 500 error
When you run this application and make a request to /ping
, you'll see logs in your terminal showing the request details, and the response will be:
{
"message": "pong"
}
Common Built-in Middleware
Gin offers several useful middleware functions out of the box:
- Logger: Logs request details
- Recovery: Recovers from panics
- BasicAuth: Provides HTTP basic authentication
- CORS: Handles Cross-Origin Resource Sharing
Example of using BasicAuth middleware:
r := gin.New()
// Group using basic auth
authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
"admin": "password123",
"user": "secret",
}))
// Protected endpoint
authorized.GET("/secrets", func(c *gin.Context) {
user := c.MustGet(gin.AuthUserKey).(string)
c.JSON(200, gin.H{
"user": user,
"message": "You have access to secrets!",
})
})
Creating Custom Middleware
Creating your own middleware allows you to implement custom logic for your application.
Basic Structure
The basic structure of a Gin middleware function is:
func MyMiddleware() gin.HandlerFunc {
// Do some initialization if needed
return func(c *gin.Context) {
// Pre-processing logic
c.Next() // Process the request
// Post-processing logic (executed after the request is processed)
}
}
Example: Request Timer Middleware
Let's create a simple middleware that measures how long each request takes to process:
package main
import (
"log"
"time"
"github.com/gin-gonic/gin"
)
func TimerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Pre-processing: record start time
startTime := time.Now()
// Process request
c.Next()
// Post-processing: calculate duration
duration := time.Since(startTime)
// Log duration
log.Printf("Request to %s took %v", c.Request.URL.Path, duration)
}
}
func main() {
r := gin.New() // Create a router without any middleware
// Use our custom middleware
r.Use(TimerMiddleware())
// Add a route
r.GET("/slow", func(c *gin.Context) {
time.Sleep(500 * time.Millisecond) // Simulate slow processing
c.JSON(200, gin.H{"message": "Slow response"})
})
r.GET("/fast", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "Fast response"})
})
r.Run(":8080")
}
Running this example and making requests to /slow
and /fast
will show log messages like:
2023/05/15 15:30:45 Request to /slow took 501.234ms
2023/05/15 15:30:48 Request to /fast took 0.345ms
Example: Authentication Middleware
Here's a more practical example - a middleware that checks for a simple API key in request headers:
package main
import (
"github.com/gin-gonic/gin"
)
func APIKeyMiddleware(validAPIKey string) gin.HandlerFunc {
return func(c *gin.Context) {
// Get API key from header
apiKey := c.GetHeader("X-API-Key")
// Check if API key is valid
if apiKey != validAPIKey {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized: Invalid API key"})
return
}
// API key is valid, continue
c.Next()
}
}
func main() {
r := gin.Default()
// Public routes
r.GET("/public", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "This is a public endpoint"})
})
// Private routes with API key authentication
private := r.Group("/api")
private.Use(APIKeyMiddleware("your-secret-api-key"))
{
private.GET("/data", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "You accessed protected data!",
"data": []string{"secret1", "secret2", "secret3"},
})
})
}
r.Run(":8080")
}
Testing this API:
- Request to
/public
will always succeed - Request to
/api/data
without theX-API-Key
header will return a 401 error - Request to
/api/data
with the headerX-API-Key: your-secret-api-key
will succeed
Middleware Chains and Order
In Gin, middleware functions are executed in the order they're added. You can add middleware at different levels:
- Global middleware - Applied to every request
- Group middleware - Applied to specific route groups
- Route middleware - Applied to specific routes
Example of Middleware Chaining
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func Middleware1() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("Before Middleware 1")
c.Next()
fmt.Println("After Middleware 1")
}
}
func Middleware2() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("Before Middleware 2")
c.Next()
fmt.Println("After Middleware 2")
}
}
func main() {
r := gin.New()
// Global middleware
r.Use(Middleware1())
// Group middleware
api := r.Group("/api")
api.Use(Middleware2())
// Route handler
api.GET("/test", func(c *gin.Context) {
fmt.Println("Executing handler")
c.JSON(200, gin.H{"message": "Success"})
})
r.Run(":8080")
}
When you make a request to /api/test
, you'll see the following output in the console:
Before Middleware 1
Before Middleware 2
Executing handler
After Middleware 2
After Middleware 1
This demonstrates the "onion-like" nature of middleware - each layer gets to process the request before and after the inner layers.
Skipping Middleware
Sometimes you might want to skip remaining middleware. You can do this using c.Abort()
:
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token != "valid-token" {
// Return unauthorized response
c.AbortWithStatusJSON(401, gin.H{
"error": "Unauthorized",
})
// Skip all remaining middleware
return
}
// Continue to next middleware/handler
c.Next()
}
}
Real-World Use Cases
CORS Middleware
Cross-Origin Resource Sharing (CORS) middleware is essential for APIs that are accessed from different domains:
package main
import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"time"
)
func main() {
r := gin.Default()
// CORS middleware configuration
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
// Routes
r.GET("/api/data", func(c *gin.Context) {
c.JSON(200, gin.H{"data": "This can be accessed cross-origin"})
})
r.Run(":8080")
}
Logging and Request ID Middleware
For production applications, it's useful to have detailed logging with unique request IDs:
package main
import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"log"
"time"
)
// RequestIDMiddleware adds a unique ID to each request
func RequestIDMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
requestID := uuid.New().String()
c.Set("RequestID", requestID)
c.Header("X-Request-ID", requestID)
c.Next()
}
}
// DetailedLogger logs request details with the request ID
func DetailedLogger() gin.HandlerFunc {
return func(c *gin.Context) {
// Start time
startTime := time.Now()
// Process request
c.Next()
// End time
endTime := time.Now()
latency := endTime.Sub(startTime)
// Get request details
requestID, _ := c.Get("RequestID")
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
path := c.Request.URL.Path
log.Printf(
"[%s] %s | %3d | %v | %s | %s",
requestID,
method,
statusCode,
latency,
clientIP,
path,
)
}
}
func main() {
r := gin.New() // No default middleware
// Add our custom middleware
r.Use(RequestIDMiddleware())
r.Use(DetailedLogger())
r.Use(gin.Recovery()) // Still use Recovery for safety
r.GET("/api/test", func(c *gin.Context) {
requestID, _ := c.Get("RequestID")
c.JSON(200, gin.H{
"message": "Hello, world!",
"request_id": requestID,
})
})
r.Run(":8080")
}
Summary
In this guide, we've covered the fundamentals of Gin middleware:
- What middleware is and how it works in the Gin framework
- How to use built-in middleware like Logger and Recovery
- Creating your own custom middleware functions
- Middleware chaining and execution order
- Real-world use cases for middleware
Middleware is a powerful concept that allows you to separate concerns in your web application. You can use middleware for authentication, logging, error handling, CORS, and many other cross-cutting concerns.
Additional Resources
- Gin Framework Official Documentation
- Gin Middleware Examples on GitHub
- Gin-contrib Repository - A collection of useful middleware for Gin
Exercises
- Create a middleware that limits requests to 10 per minute per IP address.
- Create a middleware that adds custom security headers to all responses.
- Implement a middleware that logs all request parameters (query parameters, form data, JSON body) to the console.
- Create a middleware that checks for a specific cookie, and if not present, redirects to a login page.
- Modify the authentication middleware to read API keys from a database instead of using a hardcoded value.
Happy coding with Gin middleware!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)