Echo — bind, validasi & error terpusat
Echo memberi tiga hal yang benar-benar mengurangi kode di API besar: bind otomatis, validasi terpasang, dan satu error handler global. Harganya adalah tipe konteks sendiri.
Intisari
- Handler Echo mengembalikan
error— dan itu masuk ke satu penangan error terpusat. c.Bind(&req)mengisi struct dari JSON, form, query, dan parameter jalur sekaligus.- Tipe
echo.Contextberarti handler-mu tidak lagihttp.HandlerFunc— ada adaptor, tapi kodenya terikat. - Grup rute dan middleware setara chi, dengan sintaks yang sedikit lebih ringkas.
- Cocok saat sebagian besar endpoint-mu adalah "parse, validasi, panggil service, balas JSON".
Bentuknya
import "github.com/labstack/echo/v4"
func main() {
e := echo.New()
e.HideBanner = true
e.HTTPErrorHandler = penanganError // satu tempat untuk semua error
e.Use(middleware.RequestID())
e.Use(middleware.Recover())
e.Use(middleware.Secure())
e.Use(middleware.RateLimiter(
middleware.NewRateLimiterMemoryStore(20)))
api := e.Group("/api/v1")
api.GET("/produk", h.DaftarProduk)
api.GET("/produk/:id", h.AmbilProduk)
priv := api.Group("", h.WajibLogin)
priv.POST("/produk", h.BuatProduk)
priv.PUT("/produk/:id", h.UbahProduk)
e.Logger.Fatal(e.Start(":8080"))
}
Handler yang mengembalikan error
type buatProdukReq struct {
Nama string `json:"nama" validate:"required,min=3,max=200"`
Harga int64 `json:"harga" validate:"required,gt=0"`
}
func (h *Handler) BuatProduk(c echo.Context) error {
var req buatProdukReq
// Bind mengisi dari JSON/form/query/path sekaligus.
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "body tidak valid")
}
if err := c.Validate(&req); err != nil {
return err
}
p, err := h.produk.Buat(c.Request().Context(), req.Nama, req.Harga)
if err != nil {
return err // ← langsung ke HTTPErrorHandler
}
return c.JSON(http.StatusCreated, keResp(p))
}
Ini perbedaan bentuk yang paling terasa dari net/http. Karena handler
mengembalikan error, tidak ada lagi pasangan "tulis respons lalu return" yang
bisa lupa return-nya (Fase 3). Compiler tidak menjamin apa pun, tapi bentuknya jauh lebih
sulit disalahgunakan.
Penanganan error terpusat
func penanganError(err error, c echo.Context) {
if c.Response().Committed {
return // respons sudah terkirim
}
status, pesan := http.StatusInternalServerError, "terjadi kesalahan"
var (
he *echo.HTTPError
ve *domain.ErrValidasi
)
switch {
case errors.As(err, &he):
status = he.Code
pesan = fmt.Sprint(he.Message)
case errors.Is(err, domain.ErrTidakDitemukan):
status, pesan = http.StatusNotFound, "tidak ditemukan"
case errors.Is(err, domain.ErrTidakBerhak):
status, pesan = http.StatusForbidden, "tidak berhak"
case errors.As(err, &ve):
status, pesan = http.StatusUnprocessableEntity, ve.Error()
}
if status >= 500 {
slog.ErrorContext(c.Request().Context(), "permintaan gagal",
"err", err, "path", c.Path())
}
_ = c.JSON(status, map[string]string{"error": pesan})
}
Perhatikan c.Path(), bukan c.Request().URL.Path. Yang pertama
memberi pola rute (/produk/:id), yang kedua memberi URL sungguhan
(/produk/58213). Untuk log dan metrik, selalu yang pertama (Fase 3).
Memasang validator
type pemvalidasi struct{ v *validator.Validate }
func (p *pemvalidasi) Validate(i any) error {
if err := p.v.Struct(i); err != nil {
return &domain.ErrValidasi{Bidang: keBidang(err)}
}
return nil
}
e.Validator = &pemvalidasi{v: validator.New()}
Yang perlu diperhatikan
| Hal | Catatan |
|---|---|
c.Bind mengisi dari banyak sumber | Query bisa menimpa body. Pakai DTO khusus, jangan struct domain (Fase 1) |
Parameter memakai :id | Bukan {id} seperti ServeMux dan chi |
| Kunci konteks Echo | c.Set/c.Get terpisah dari context.Context; untuk lapisan bawah, tetap pakai c.Request().Context() |
| Batas ukuran body | Pasang middleware.BodyLimit("1M") — tidak aktif secara bawaan |
| Migrasi keluar | Handler terikat echo.Context; pindah ke chi berarti menulis ulang tanda tangannya |
Aturan yang menjaga keterikatan tetap tipis: biarkan handler Echo hanya melakukan bind, validasi, dan pemanggilan service. Selama seluruh logika ada di paket domain yang tidak tahu Echo (Fase 5), berpindah framework nanti berarti menulis ulang lapisan tertipis di aplikasimu — bukan aplikasimu.
Latihan: tulis satu endpoint POST dengan bind + validasi Echo, lalu kirim body yang melanggar
dua aturan sekaligus dan pastikan responsnya menyebut keduanya. Lalu kembalikan
domain.ErrTidakDitemukan dari service dan buktikan penangan terpusat mengubahnya jadi 404
tanpa satu baris pun di handler.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.