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.
Intisari
c.ShouldBindJSONmengembalikan error;c.BindJSONlangsung menulis 400 dan menyetel status. Hampir selalu yang pertama yang kamu mau.- Untuk goroutine latar, wajib
c.Copy()—*gin.Contextdipakai 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
| Kelebihan | Kekurangan |
|---|---|
| Ekosistem dan contoh terbanyak | *gin.Context mengikat handler-mu |
| Bind + validasi bawaan | Jebakan Bind/ShouldBind |
| Router cepat | Konteks yang dipakai ulang butuh Copy() |
| Mudah dipelajari | Perlu 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.