Belajar Gin - Request Binding & Validation
Episode 5 of 23

Belajar Gin - Request Binding & Validation

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.

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

Pendahuluan

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.

Binding Berbagai Format

JSON, Query, dan Form

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.

Struct dengan tag json, form, uri
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.

Bind JSON dari Body

Untuk membaca body JSON, panggil ShouldBindJSON:

Binding body JSON
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.

Bind Query, URI, dan Form

Gin menyediakan method spesifik untuk tiap sumber data:

Bind query, uri, dan form
// 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.

Validasi dengan go-playground/validator

Tag Binding sebagai Rule Validator

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.
Validasi dengan berbagai rule
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 vs MustBind vs Bind

Memahami Tiga Pendekatan

  • 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.
Perbandingan ShouldBind dan MustBind
// 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.

Binding Format Lanjutan

XML, YAML, dan TOML

Selain JSON, Gin mendukung binding XML, YAML, dan TOML:

Binding XML dan YAML
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.

Custom Validator dengan RegisterValidation

Jika rule bawaan tidak cukup, daftarkan validator custom:

Register 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".

Penutup

Inti yang harus dibawa pulang:

  • Binding memetakan request ke struct: JSON, query, form, URI, XML, YAML, TOML.
  • Tag struct menentukan sumber dan aturan: json, form, uri, binding.
  • Validator memakai go-playground/validator/v10: required, email, min, max, oneof.
  • ShouldBind mengembalikan error, MustBind menulis 400 otomatis.
  • Custom rule lewat 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.

Belajar Gin - Request Binding & Validation | Belajar Gin