← Semua pembelajaran / Go Nol → Enterprise
Fase 7 · Framework Web & Frontend

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.Context berarti handler-mu tidak lagi http.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

HalCatatan
c.Bind mengisi dari banyak sumberQuery bisa menimpa body. Pakai DTO khusus, jangan struct domain (Fase 1)
Parameter memakai :idBukan {id} seperti ServeMux dan chi
Kunci konteks Echoc.Set/c.Get terpisah dari context.Context; untuk lapisan bawah, tetap pakai c.Request().Context()
Batas ukuran bodyPasang middleware.BodyLimit("1M") — tidak aktif secara bawaan
Migrasi keluarHandler 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.