Learn Gin - Middleware
Series/Learn Gin/Episode 6
Episode 6 of 23

Learn Gin - Middleware

This episode dissects Gin middleware: writing your own middleware with c.Next() and c.Abort(), installing it with engine.Use(), using built-in middleware such as Logger, Recovery, BasicAuth, CORS, and gzip, and controlling the order of execution.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Middleware is one of the main reasons people choose Gin. With middleware, you run logic before and after a handler without modifying the handler code itself: logging, authentication, CORS, recovery, compression — all of it can be encapsulated into pieces you can attach to any route.

Episode 6 dissects middleware from two angles: how to write your own custom middleware, and how to take advantage of the most commonly used built-in Gin middleware. Understanding execution order will save you from bugs that are hard to track down.

Middleware Basics

The Anatomy of Gin Middleware

Middleware is just a HandlerFunc: a function that receives *gin.Context. Two key methods control the flow:

  • c.Next(): continue execution to the next handler; code after c.Next() runs after the handler finishes.
  • c.Abort(): stop the chain; the next handlers aren't called.
Middleware anatomy
func myMiddleware(c *gin.Context) {
    // kode sebelum handler
    c.Next()
    // kode setelah handler selesai
}

The code before c.Next() runs pre-processing logic, and the code after it runs post-processing logic. This is what the Logger middleware uses to record request duration.

Writing a Logging Middleware

Let's write a simple logging middleware that records the method, path, and duration:

Custom logging middleware
func requestLogger() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        duration := time.Since(start)
        log.Printf("%s %s -> %d (%s)",
            c.Request.Method,
            c.Request.URL.Path,
            c.Writer.Status(),
            duration,
        )
    }
}

The function c.Writer.Status() returns the status code the handler wrote. This middleware records the request after the handler finishes because the c.Next() call sits in the middle.

engine.Use and Abort

Install middleware on the whole engine or stop the flow with Abort:

Use and Abort
r := gin.New()
r.Use(requestLogger())
 
func requireToken(c *gin.Context) {
    token := c.GetHeader("Authorization")
    if token == "" {
        c.AbortWithStatusJSON(401, gin.H{"error": "token wajib"})
        return
    }
    c.Set("token", token)
    c.Next()
}

c.AbortWithStatusJSON(401, ...) stops the chain while writing a JSON response. The function c.Set("token", token) stores a value that the next handler can read with c.Get.

Built-in Middleware

Logger and Recovery

gin.Default() already includes Logger and Recovery. Logger writes an access log for every request; Recovery catches panics in handlers so the server doesn't die:

Explicit Logger and Recovery
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())

If you don't use gin.Default(), install both explicitly. Recovery is critical in production: an unhandled panic will stop the server process.

BasicAuth

For simple protection, Gin provides the BasicAuth middleware:

Built-in BasicAuth
r.Use(gin.BasicAuth(gin.Accounts{
    "arman": "rahasia123",
    "dev":   "rahasia456",
}))
 
r.GET("/admin", func(c *gin.Context) {
    user := c.MustGet(gin.AuthUserKey).(string)
    c.JSON(200, gin.H{"user": user})
})

The function c.MustGet(gin.AuthUserKey).(string) retrieves the validated username. This middleware compares credentials in a way that's safe from timing attacks. For more serious systems, episode 13 will cover JWT and sessions.

CORS with gin-contrib/cors

If your API is called from a browser on a different domain, CORS is required:

Install the CORS middleware
go get github.com/gin-contrib/cors
CORS configuration
import "github.com/gin-contrib/cors"
 
r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://app.example.com"},
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
    AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
}))

The AllowOrigins configuration restricts which domains may access your API. Don't use a wildcard * if AllowCredentials is active — that combination is rejected by browsers.

For bandwidth compression, the gin-contrib ecosystem provides a gzip middleware installed with r.Use(gzip.Gzip(gzip.DefaultCompression)). JSON and text responses are compressed automatically when the client sends an Accept-Encoding: gzip header.

Execution Order

LIFO for Post-Handler Code

The order of Use determines the order of execution. Code after c.Next() runs in reverse order (LIFO):

Middleware execution order
r.Use(m1) // masuk pertama
r.Use(m2) // masuk kedua
r.GET("/ping", handler)
 
// alur: m1 -> m2 -> handler -> m2 (sisa) -> m1 (sisa)

Pre-processing logic runs from the first registered middleware to the last, while post-processing logic runs in reverse. A real-world example: put Recovery outermost so it can catch panics from middleware inside it.

Per-Group Middleware

Not all middleware needs to be global. Install middleware on specific groups:

Per-group middleware
api := r.Group("/api")
api.Use(requestLogger())
 
private := api.Group("/private")
private.Use(requireToken)
 
private.GET("/profile", func(c *gin.Context) {
    c.JSON(200, gin.H{"status": "profil dilindungi"})
})

The route /api/private/profile inherits requestLogger from the api group and requireToken from the private group. This hierarchy keeps middleware scoped appropriately.

Closing

Key takeaways:

  • Middleware is a HandlerFunc: c.Next() continues, c.Abort() stops.
  • Post-Next() logic runs after the handler finishes.
  • gin.Default() = Logger + Recovery; install them explicitly if using gin.New().
  • BasicAuth and CORS are built-in/ecosystem middleware you must know.
  • The order of Use determines execution order; post-processing runs LIFO.
  • Middleware can be installed globally, per group, or per route.

In the next episode, episode 7, we'll dissect response rendering & static files — returning JSON, XML, YAML, TOML, and ProtoBuf, rendering HTML templates, redirects, static files, file uploads, and streaming responses.

Learn Gin - Middleware | Learn Gin