Episode ini membedah proses binding dan validation di Echo: cara c.Bind mengubah JSON, XML, atau form menjadi struct, tag struct untuk query dan URI binding, binder custom, integrasi validator go-playground, dan penanganan error 400 secara terpusat.

Setiap REST API menerima input dari klien — dan input adalah titik paling rawan kesalahan. Echo menyelesaikan dua masalah ini sekaligus: binding untuk mengubah payload menjadi struct Go, dan validation untuk memastikan data yang masuk valid sebelum menyentuh logika bisnis.
Episode 5 ini membedah keduanya: cara c.Bind menangani JSON, XML, dan form; tag struct untuk query serta URI binding; binder custom untuk format khusus; validator pluggable go-playground/validator; dan pola penanganan error 400 yang terpusat.
c.Bind membaca body request dan mengisinya ke struct yang kalian sediakan. Tipe payload dideteksi otomatis dari header Content-Type:
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
e.POST("/users", func(c echo.Context) error {
req := new(CreateUserRequest)
if err := c.Bind(req); err != nil {
return err
}
return c.JSON(http.StatusCreated, req)
})Saat klien mengirim JSON {"name":"Arman","email":"arman@dev.id"}, c.Bind mengisi struct dan mengembalikan error jika payload tidak cocok. Perhatikan pola c.Bind(req) — struct harus dioper sebagai pointer.
Binder default Echo menangani beberapa format sekaligus:
Content-Type: application/json.Content-Type: application/xml.Content-Type: application/x-www-form-urlencoded.multipart/form-data.curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"name":"Arman","email":"arman@dev.id"}'Binding tidak hanya dari body. Echo mendukung tag query dan param sehingga path parameter dan query string juga ter-binding langsung ke struct:
type ListFilter struct {
Page int `query:"page"`
Search string `query:"q"`
UserID string `param:"id"`
}
e.GET("/users/:id", func(c echo.Context) error {
f := new(ListFilter)
if err := c.Bind(f); err != nil {
return err
}
return c.JSON(http.StatusOK, f)
})Dengan c.Bind(f), field Page terisi dari ?page=, Search dari ?q=, dan UserID dari path parameter :id. Satu panggilan binding menangani semua sumber input.
Perhatikan bahwa c.Bind hanya mengurai data — dia tidak memvalidasi makna. Payload {"name":"","email":"bukan-email"} akan berhasil di-bind, dan di sinilah validator berperan.
Echo tidak membawa validator bawaan; dia menyediakan antarmuka yang bisa diisi library apa pun. Yang paling umum adalah go-playground/validator. Hubungkan lewat properti e.Validator:
import (
"github.com/go-playground/validator/v10"
)
type CustomValidator struct {
validator *validator.Validate
}
func (cv *CustomValidator) Validate(i interface{}) error {
return cv.validator.Struct(i)
}
e.Validator = &CustomValidator{validator: validator.New()}Struktur CustomValidator mengimplementasikan antarmuka echo.Validator dengan method Validate. Setelah ini, tag validasi pada struct langsung diproses.
go-playground/validator menyediakan ratusan aturan lewat struct tag. Berikan tag seperti required, email, dan gte:
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"gte=0,lte=130"`
}Aturan required,min=3 berarti wajib diisi minimal tiga karakter, email memvalidasi format email, dan gte/lte membatasi rentang angka. Validator berjalan otomatis ketika handler memanggil c.Validate.
Pola yang benar adalah bind dulu, validasi setelahnya:
e.POST("/users", func(c echo.Context) error {
req := new(CreateUserRequest)
if err := c.Bind(req); err != nil {
return err
}
if err := c.Validate(req); err != nil {
return err
}
return c.JSON(http.StatusCreated, req)
})c.Validate(req) memanggil validator yang sudah didaftarkan dan mengembalikan error validasi bila ada aturan yang gagal.
Untuk kebutuhan khusus, daftarkan fungsi validasi sendiri:
cv.validator.RegisterValidation("strong", func(fl validator.FieldLevel) bool {
value := fl.Field().String()
return len(value) >= 8 && value != strings.ToLower(value)
})Fungsi ini menambahkan aturan baru bernama strong yang bisa dipakai di tag struct manapun: validate:"required,strong".
Echo membedakan error menjadi dua kelompok: HTTPError yang sudah punya status code, dan error biasa. Error dari binder dan validator adalah error biasa sehingga defaultnya berubah menjadi 500. Agar API konsisten, kenali kedua tipe ini dan ubah ke 400:
if err := c.Bind(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "format payload tidak valid")
}
if err := c.Validate(req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}Dengan pola ini, setiap kesalahan input menghasilkan respons 400 yang konsisten.
Episode 5 membuat kalian menguasai pipeline input Echo: c.Bind mengubah JSON, XML, atau form menjadi struct; tag query dan param menangkap input dari URL; go-playground/validator memvalidasi makna data; aturan custom menangani kebutuhan khusus; dan error 400 ditangani secara terpusat.
Inti yang harus dibawa pulang:
c.Bind mengurai body sesuai Content-Type ke struct pointer.query dan param mengikat nilai dari URL ke struct.c.Bind hanya mengurai; c.Validate yang memeriksa makna.validate seperti required dan email dijalankan otomatis.HTTPError 400.Di episode 6 selanjutnya kita akan membahas middleware — cara menulis middleware dengan c.Next(), pemasangan di root, group, dan route, pre-middleware, serta middleware bawaan seperti CORS, JWT, BodyLimit, Gzip, dan RateLimit.