Episode ini membedah binding request Gin: JSON, query, form, URI, XML, YAML, dan TOML, pemahaman tag struct json, form, uri, binding, library validator go-playground/validator/v10, serta perbedaan ShouldBind, MustBind, dan Bind.

Di episode 4 kalian sudah bisa merancang URL API. Sekarang masuk ke bagian yang sering diremehkan tapi paling sering menimbulkan bug: memetakan data dari request ke struct (binding) dan memastikan datanya valid (validation). Kalau dua hal ini tidak ditangani dengan benar, API kalian akan menerima data kotor.
Episode 5 membedah binding secara menyeluruh: berbagai format yang didukung Gin, tag struct yang mengontrol pemetaan, library validator yang menegakkan aturan, serta kapan harus memakai ShouldBind, MustBind, atau Bind.
Gin memetakan data request ke struct melalui tag yang sesuai. Data JSON dibaca dari body, query dibaca dari URL, dan form dibaca dari body application/x-www-form-urlencoded atau multipart.
type SearchRequest struct {
Query string `json:"query" form:"q" uri:"q" binding:"required"`
Page int `json:"page" form:"page" uri:"page" binding:"gte=1"`
Limit int `json:"limit" form:"limit" uri:"limit" binding:"min=1,max=100"`
}
type CreateUser struct {
Name string `json:"name" form:"name" binding:"required,min=3"`
Email string `json:"email" form:"email" binding:"required,email"`
Age int `json:"age" form:"age" binding:"gte=17,lte=100"`
}Tag form:"q" membuat field bisa diisi lewat query atau form. Tag uri:"q" memungkinkan pengisian lewat path parameter. Struktur seperti ini adalah fondasi REST API yang rapi.
Untuk membaca body JSON, panggil ShouldBindJSON:
func createUserHandler(c *gin.Context) {
var user CreateUser
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(201, gin.H{"name": user.Name, "email": user.Email})
}Fungsi c.ShouldBindJSON(&user) membaca c.Request.Body, men-decode JSON ke struct, lalu menjalankan validasi. Jika Content-Type tidak cocok, gunakan pendekatan yang akan kita bahas sebentar lagi.
Gin menyediakan method spesifik untuk tiap sumber data:
// query: /search?q=golang&page=2&limit=20
var q SearchRequest
c.ShouldBindQuery(&q)
// uri: /search/golang/2/20
c.ShouldBindUri(&q)
// form: body application/x-www-form-urlencoded
var form CreateUser
c.ShouldBind(&form)c.ShouldBind(&form) memilih binder otomatis berdasarkan Content-Type request: JSON, XML, atau form. Sementara ShouldBindQuery, ShouldBindUri, dan ShouldBindJSON memaksa format tertentu.
Gin memakai library go-playground/validator/v10 di balik layar. Tag binding mengatur aturan validasi yang langsung diterapkan setelah binding berhasil:
required — field wajib diisi.min=3, max=20 — panjang minimum dan maksimum.email — harus berupa alamat email valid.gte=1,lte=100 — nilai harus dalam rentang.oneof=admin user — nilai harus salah satu dari daftar.type RegisterInput struct {
Username string `json:"username" binding:"required,min=4,max=16,alphanum"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
Role string `json:"role" binding:"oneof=user admin"`
Score int `json:"score" binding:"gte=0,lte=100"`
}Tag oneof=user admin memastikan field Role hanya berisi user atau admin. Kombinasi min, max, email, dan alphanum menjaga input tetap bersih sejak awal.
Untuk data kompleks, validator juga menembus struct bertingkat dengan tag dive. Tag dive memerintahkan validator memvalidasi setiap elemen slice; tanpa dive, validasi hanya memeriksa slice itu sendiri, bukan isinya.
ShouldBind*: mengembalikan error, tidak mengubah status response; kalian menangani errornya sendiri.MustBind*: jika error, langsung mengembalikan 400 dengan pesan error JSON; code setelahnya tidak dieksekusi.Bind: versi lama (deprecated sejak Gin v1.14 untuk banyak kasus), otomatis memilih binder berdasarkan Content-Type dan menulis 400 saat error.// pola yang disarankan
var user CreateUser
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(400, gin.H{"error": "data tidak valid"})
return
}
// pola cepat, tidak fleksibel
c.MustBindWith(&user, binding.JSON)
c.JSON(200, gin.H{"user": user})c.MustBindWith(&user, binding.JSON) menulis response 400 otomatis saat binding gagal. Untuk kontrol penuh atas pesan error, ShouldBindJSON tetap pilihan terbaik.
Validator mengembalikan error bertipe validator.ValidationErrors. Dengan errors.As(err, &verrs) kalian bisa memeriksa apakah error berasal dari validator, lalu membaca detail tiap FieldError seperti field, tag, dan nilai yang gagal — berguna untuk membangun response error yang konsisten di seluruh API.
Selain JSON, Gin mendukung binding XML, YAML, dan TOML:
var xmlData MyXML
c.ShouldBindXML(&xmlData)
var yamlData MyYAML
c.ShouldBindBodyWithYAML(&yamlData)
var tomlData MyTOML
c.ShouldBindBodyWithTOML(&tomlData)Metode c.ShouldBindBodyWithYAML(&yamlData) dan c.ShouldBindBodyWithTOML tersedia sejak Gin v1.10. Kelompok ShouldBindBodyWith* juga bisa dipanggil berkali-kali dalam satu handler karena menyimpan salinan body di Context — berbeda dari ShouldBind yang hanya bisa membaca body sekali.
Jika rule bawaan tidak cukup, daftarkan validator custom:
v, ok := binding.Validator.Engine().(*validator.Validate)
if ok {
v.RegisterValidation("matauang", func(fl validator.FieldLevel) bool {
return fl.Field().String() == "IDR" || fl.Field().String() == "USD"
})
}
type Payment struct {
Currency string `json:"currency" binding:"required,matauang"`
}Fungsi binding.Validator.Engine() mengembalikan instance validator yang dipakai Gin, sehingga rule matauang langsung tersedia untuk semua tag binding:"matauang".
Inti yang harus dibawa pulang:
json, form, uri, binding.required, email, min, max, oneof.ShouldBind mengembalikan error, MustBind menulis 400 otomatis.RegisterValidation.ShouldBindBodyWith* bisa membaca body berkali-kali dalam satu handler.Di episode 6 selanjutnya kita akan membedah middleware — menulis middleware sendiri dengan c.Next() dan c.Abort(), memakai middleware bawaan seperti Logger, Recovery, BasicAuth, CORS, dan gzip, serta mengatur urutan eksekusi.