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

Gin — populer, dan hal yang perlu diwaspadai

Gin punya ekosistem terbesar dan contoh kode terbanyak di internet. Itu keunggulan nyata untuk tim — asalkan kamu tahu tiga hal yang paling sering salah dipakai.

Sumber asli gin-gonic.com Resmi Rangkuman ~6 menit baca

Intisari

  • c.ShouldBindJSON mengembalikan error; c.BindJSON langsung menulis 400 dan menyetel status. Hampir selalu yang pertama yang kamu mau.
  • Untuk goroutine latar, wajib c.Copy() — *gin.Context dipakai ulang setelah permintaan selesai.
  • gin.Default() memasang Logger + Recovery; di produksi biasanya kamu ingin milikmu sendiri.
  • Setel gin.SetMode(gin.ReleaseMode) di produksi — mode debug lambat dan bocor informasi.
  • Untuk lapisan bawah, teruskan c.Request.Context(), bukan *gin.Context.

Bentuknya

import "github.com/gin-gonic/gin"

func main() {
	gin.SetMode(gin.ReleaseMode)

	r := gin.New()                     // BUKAN gin.Default()
	r.Use(gin.Recovery())
	r.Use(logSlog(logger))             // logger sendiri, terstruktur

	api := r.Group("/api/v1")
	api.GET("/produk", h.DaftarProduk)
	api.GET("/produk/:id", h.AmbilProduk)

	priv := api.Group("", h.WajibLogin)
	priv.POST("/produk", h.BuatProduk)

	srv := &http.Server{Addr: ":8080", Handler: r /* timeout... */}
	srv.ListenAndServe()
}

gin.Default() memasang logger bawaan yang menulis teks berwarna ke stdout. Di CloudWatch, itu berarti baris log yang tidak bisa di-query dan penuh kode ANSI. Pakai gin.New() lalu pasang middleware log terstrukturmu sendiri (Fase 5).

Jebakan 1: Bind versus ShouldBind

// ❌ Bind* menulis respons 400 SENDIRI dan memanggil c.AbortWithError.
//    Kamu kehilangan kendali atas bentuk pesan errornya, dan mudah
//    menghasilkan respons ganda kalau kamu juga menulis sendiri.
if err := c.BindJSON(&req); err != nil {
	c.JSON(400, gin.H{"error": err.Error()})   // ← 400 KEDUA
	return
}

// ✅ ShouldBind* hanya mengembalikan error. Kamu yang memutuskan.
if err := c.ShouldBindJSON(&req); err != nil {
	c.JSON(http.StatusBadRequest, gin.H{"error": "body tidak valid"})
	return
}

Dan jangan memantulkan err.Error() dari binding ke pengguna. Pesannya memuat nama field Go, tipe internal, dan potongan input — informasi yang berguna untuk penyerang dan tidak berguna untuk pengguna (Fase 6).

Jebakan 2: context di goroutine

// ❌ *gin.Context dipakai ulang setelah permintaan selesai.
//    Goroutine ini akan membaca data permintaan ORANG LAIN.
func (h *Handler) Buat(c *gin.Context) {
	go func() {
		kirimEmail(c.GetString("email"))    // race + data yang salah
	}()
}

// ✅ Salin dulu
func (h *Handler) Buat(c *gin.Context) {
	cc := c.Copy()
	go func() {
		kirimEmail(cc.GetString("email"))
	}()
}

Ini kebocoran data antar pengguna, bukan sekadar bug. Gin memakai sync.Pool untuk objek konteks — objek yang sama dipakai lagi untuk permintaan berikutnya begitu handler selesai. Sebuah goroutine yang masih memegangnya akan membaca isi permintaan pengguna lain. Dan sesuai Fase 2, pekerjaan latar sungguhan seharusnya masuk antrean, bukan go func().

Jebakan 3: context yang diteruskan ke bawah

// ❌ *gin.Context memenuhi context.Context, tapi pembatalannya
//    tidak sama dengan pembatalan permintaan.
h.svc.Ambil(c, id)

// ✅ context permintaan yang sebenarnya
h.svc.Ambil(c.Request.Context(), id)

Binding dan validasi

type buatProdukReq struct {
	Nama  string `json:"nama"  binding:"required,min=3,max=200"`
	Harga int64  `json:"harga" binding:"required,gt=0"`
}

func (h *Handler) BuatProduk(c *gin.Context) {
	var req buatProdukReq
	if err := c.ShouldBindJSON(&req); err != nil {
		h.tulisError(c, &domain.ErrValidasi{Bidang: keBidang(err)})
		return
	}

	p, err := h.produk.Buat(c.Request.Context(), req.Nama, req.Harga)
	if err != nil {
		h.tulisError(c, err)
		return
	}
	c.JSON(http.StatusCreated, keResp(p))
}

Gin memakai tag binding, bukan validate — meski di baliknya adalah pustaka validator yang sama. Salah satu penyebab paling umum "validasinya tidak jalan" adalah menyalin struct dari contoh Echo tanpa mengganti nama tagnya.

Ringkasan

KelebihanKekurangan
Ekosistem dan contoh terbanyak*gin.Context mengikat handler-mu
Bind + validasi bawaanJebakan Bind/ShouldBind
Router cepatKonteks yang dipakai ulang butuh Copy()
Mudah dipelajariPerlu disiplin agar tidak menempel ke seluruh kode

Kalau timmu sudah memakai Gin, tetap di Gin — biaya migrasi tidak terbayar oleh keuntungan apa pun yang ditawarkan alternatifnya. Untuk proyek baru, roadmap ini tetap merekomendasikan net/http plus chi, karena handler-nya tidak terikat pustaka mana pun.

Latihan: tulis handler Gin yang menjalankan go func() membaca c.GetString("pengguna") tanpa Copy(), lalu tembak endpoint itu dengan hey -n 200 -c 20 sambil menjalankan -race. Baca laporan race-nya, lalu perbaiki dengan c.Copy().

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.