Episode ini membedah middleware Gin: menulis middleware sendiri dengan c.Next() dan c.Abort(), memasangnya dengan engine.Use(), memakai middleware bawaan seperti Logger, Recovery, BasicAuth, CORS, dan gzip, serta mengatur urutan eksekusi.

Middleware adalah salah satu alasan utama orang memilih Gin. Dengan middleware, kalian mengeksekusi logika sebelum dan sesudah handler tanpa mengubah kode handler itu sendiri: logging, autentikasi, CORS, recovery, kompresi — semuanya bisa di-encapsulate menjadi potongan yang bisa dipasang di rute mana pun.
Episode 6 membedah middleware dari dua sisi: bagaimana menulis middleware custom sendiri, dan bagaimana memanfaatkan middleware bawaan Gin yang paling sering dipakai. Pemahaman tentang urutan eksekusi akan menyelamatkan kalian dari bug yang sulit dilacak.
Middleware hanyalah HandlerFunc: fungsi yang menerima *gin.Context. Dua method kunci yang mengontrol alur:
c.Next(): lanjutkan eksekusi ke handler berikutnya; kode setelah c.Next() berjalan setelah handler selesai.c.Abort(): hentikan chain; handler berikutnya tidak dipanggil.func myMiddleware(c *gin.Context) {
// kode sebelum handler
c.Next()
// kode setelah handler selesai
}Kode sebelum c.Next() menjalankan logika pra-proses, dan kode setelahnya menjalankan logika pasca-proses. Inilah yang dipakai middleware Logger untuk mencatat durasi request.
Mari tulis middleware logging sederhana yang mencatat method, path, dan durasi:
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,
)
}
}Fungsi c.Writer.Status() mengembalikan status code yang ditulis handler. Middleware ini mencatat request setelah handler selesai karena panggilan c.Next() berada di tengah.
Pasang middleware ke seluruh engine atau hentikan alur dengan 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, ...) menghentikan chain sekaligus menulis response JSON. Fungsi c.Set("token", token) menyimpan nilai yang bisa dibaca handler berikutnya dengan c.Get.
gin.Default() sudah menyertakan Logger dan Recovery. Logger menulis log akses untuk setiap request; Recovery menangkap panic di handler agar server tidak mati:
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())Jika tidak memakai gin.Default(), pasang keduanya secara eksplisit. Recovery sangat penting di production: panic yang tidak ditangani akan menghentikan proses server.
Untuk proteksi sederhana, gin menyediakan middleware 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})
})Fungsi c.MustGet(gin.AuthUserKey).(string) mengambil username yang tervalidasi. Middleware ini membandingkan kredensial dengan cara yang aman dari timing attack. Untuk sistem yang lebih serius, episode 13 akan membahas JWT dan session.
Jika API kalian dipanggil dari browser di domain berbeda, CORS diperlukan:
go get github.com/gin-contrib/corsimport "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,
}))Konfigurasi AllowOrigins membatasi domain mana yang boleh mengakses API. Jangan memakai wildcard * jika AllowCredentials aktif — kombinasi itu ditolak browser.
Untuk kompresi bandwidth, ekosistem gin-contrib menyediakan middleware gzip yang dipasang dengan r.Use(gzip.Gzip(gzip.DefaultCompression)). Response JSON dan teks dikompres otomatis ketika klien mengirim header Accept-Encoding: gzip.
Urutan Use menentukan urutan eksekusi. Kode setelah c.Next() berjalan dengan urutan terbalik (LIFO):
r.Use(m1) // masuk pertama
r.Use(m2) // masuk kedua
r.GET("/ping", handler)
// alur: m1 -> m2 -> handler -> m2 (sisa) -> m1 (sisa)Logika pra-proses berjalan dari middleware yang dipasang pertama ke terakhir, sedangkan logika pasca-proses berjalan terbalik. Contoh nyata: taruh Recovery paling luar agar bisa menangkap panic dari middleware di dalamnya.
Tidak semua middleware perlu berlaku global. Pasang middleware pada grup tertentu:
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"})
})Rute /api/private/profile mewarisi requestLogger dari grup api dan requireToken dari grup private. Hierarki ini menjaga middleware tetap sesuai cakupannya.
Inti yang harus dibawa pulang:
HandlerFunc: c.Next() melanjutkan, c.Abort() menghentikan.Next() berjalan setelah handler selesai.gin.Default() = Logger + Recovery; pasang eksplisit jika memakai gin.New().Use menentukan urutan eksekusi; pasca-proses berjalan LIFO.Di episode 7 selanjutnya kita akan membedah response rendering & static files — mengembalikan JSON, XML, YAML, TOML, dan ProtoBuf, merender HTML template, redirect, file statis, upload file, dan streaming response.