Episode ini meninjau fitur stabil terbaru Gin: v1.11 dengan ShouldBindBodyWithPlain, HTTP/3 eksperimental, dan pembersihan deprecated; v1.12 dengan encoding.UnmarshalText, Protobuf, OptionFunc, serta kemudahan yang dibawa engine.With.

Sejak episode 0 kita memakai Gin v1.12.0, tetapi belum pernah meninjau apa saja yang berubah di dua rilis terakhir. Episode 20 ini membedah fitur stabil terbaru Gin: rilis v1.11.0 (September 2025) dan v1.12.0 (Februari 2026). Kalian akan melihat bagaimana binding diperkuat, apa saja yang dibersihkan dari API lama, dan fitur mana yang paling berguna untuk project kalian.
Memahami arah rilis penting karena memengaruhi keputusan upgrade. Kabar baiknya: tidak ada breaking change besar — upgrade v1.10 ke v1.12 umumnya cukup go get. Yang berubah adalah tambahan API yang membuat kode lebih ekspresif dan lebih cepat.
Gin v1.11 memperkenalkan BindPlain dan ShouldBindBodyWithPlain untuk membaca body teks mentah, plus dukungan unixMilli dan unixMicro pada binding waktu:
type Message struct {
Content string
}
func webhookHandler(c *gin.Context) {
var msg Message
if err := c.ShouldBindBodyWithPlain(&msg, binding.String); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"content": msg.Content})
}c.ShouldBindBodyWithPlain(&msg, binding.String) memetakan body plain text ke field string. Ini berguna untuk webhook yang mengirim payload teks tanpa Content-Type: application/json. Versi ini juga menandai fungsi deprecated lama untuk dirapikan, dan menambah RunQUIC untuk HTTP/3 yang masih eksperimental.
Fitur unggulan v1.12: binding uri dan query kini menghormati encoding.UnmarshalText. Artinya kalian bisa membuat tipe kustom yang mengurai nilainya sendiri:
type Clock struct {
time.Time
}
func (c *Clock) UnmarshalText(text []byte) error {
t, err := time.Parse("15:04:05", string(text))
if err != nil {
return err
}
c.Time = t
return nil
}
type ScheduleRequest struct {
Start Clock `form:"start" binding:"required"`
}var req ScheduleRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}c.ShouldBindQuery(&req) memanggil UnmarshalText pada tipe Clock saat mengisi query start=08:30:00. Sebelum v1.12, penguraian format khusus di query harus dilakukan manual setelah binding. Sekarang kustomisasi disimpan di tipe itu sendiri, sehingga aturan berlaku konsisten di semua route.
Rangkaian ShouldBindBodyWith* kini melayani lebih banyak format:
c.ShouldBindBodyWithJSON(&data)
c.ShouldBindBodyWithXML(&data)
c.ShouldBindBodyWithYAML(&data)
c.ShouldBindBodyWithTOML(&data)c.ShouldBindBodyWithYAML(&data) membaca body YAML sambil menyimpan salinan body di context — sehingga bisa dipanggil lebih dari sekali dalam satu handler, berbeda dari ShouldBind biasa. Di v1.12, penanganan body untuk TOML dan YAML juga dibenahi agar tidak mendekode ganda, dan bind body untuk Protobuf ikut didukung dalam content negotiation.
Sejak v1.10, gin.New dan gin.Default menerima OptionFunc — fungsi yang memodifikasi engine:
func withLogger(logger *slog.Logger) gin.OptionFunc {
return func(e *gin.Engine) {
e.Use(slogMiddleware(logger))
}
}
r := gin.New(withLogger(appLogger))gin.New(withLogger(appLogger)) mengonfigurasi engine di saat konstruksi, bukan setelahnya. Fungsi withLogger mengembalikan OptionFunc yang memasang middleware. Pendekatan ini membuat komposisi konfigurasi menjadi modular dan mudah diuji.
Untuk menerapkan opsi setelah engine ada, gunakan engine.With:
r := gin.New()
r.With(withLogger(appLogger), withRecovery(appLogger))engine.With(withLogger(...)) mengembalikan engine yang sudah dikonfigurasi, sehingga bisa dirantai. Pola functional options ini semakin rapi di v1.11 dan v1.12 — kalian bisa merangkai logger, recovery, dan middleware lain dalam satu baris ekspresif.
Gin kini memiliki tipe Skipper — fungsi yang menerima context dan mengembalikan boolean — untuk memutuskan apakah middleware dijalankan:
type Skipper func(c *gin.Context) bool
skipHealth := func(c *gin.Context) bool {
return c.Request.URL.Path == "/healthz"
}
r.Use(loggerMiddlewareWith(skipHealth))skipHealth membuat endpoint health dan metrics tidak membanjiri log akses — pola yang langsung berguna setelah episode 18. Di v1.12, konfigurasi logger juga memungkinkan menonaktifkan output query string dan mewarnai latensi agar request lambat mudah dikenali di terminal.
Untuk meng-upgrade project yang sudah ada:
go get github.com/gin-gonic/gin@v1.12.0
go mod tidy
go build ./...go get github.com/gin-gonic/gin@v1.12.0 menarik rilis terbaru. Karena tidak ada breaking change pada API publik yang kalian pakai, go build ./... seharusnya sukses langsung. Perhatikan bahwa v1.12 menetapkan minimum Go 1.25 — pastikan toolchain kalian memenuhi syarat sebelum upgrade.
Inti yang harus dibawa pulang:
ShouldBindBodyWithPlain dan dukungan waktu unixMilli/unixMicro.encoding.UnmarshalText untuk binding uri dan query.ShouldBindBodyWith* kini melayani JSON, XML, YAML, dan TOML secara konsisten.OptionFunc dan engine.With membuat konfigurasi engine menjadi modular.Skipper memungkinkan middleware melewati endpoint tertentu.Di episode 21 selanjutnya kita akan membedah production-ready architecture — menggabungkan semua pelajaran menjadi arsitektur production: layout modular, graceful shutdown, konfigurasi env, logging terpusat, containerization dengan Docker multi-stage, pipeline CI/CD, dan deployment zero-downtime.