Gin
Go web framework — high-performance HTTP server, middleware, RESTful API
What I Can Do
- สร้าง RESTful API ด้วย Gin ตั้งแต่ routing ถึง production-ready
- ออกแบบ middleware chain สำหรับ auth, logging, rate limiting, CORS
- Implement request validation ด้วย struct tags + binding
- จัดการ error handling แบบ centralized
- เขียน integration tests สำหรับ HTTP handlers
Commands I Use Daily
# install Gin
go get -u github.com/gin-gonic/gin
# run server ตอน dev
go run ./cmd/server
# run tests สำหรับ handler layer
go test -v ./internal/handler/...
# build binary
CGO_ENABLED=0 go build -o main ./cmd/serverRouter & Route Groups
Gin ใช้ httprouter-based routing — เร็วกว่า default net/http เพราะใช้ radix tree, r.GET("/users/:id", handler) สำหรับ path parameters, r.Group("/api/v1") จัด routes เป็นกลุ่มพร้อม shared middleware
HTTP Methods & RESTful Routing
r.GET, r.POST, r.PUT, r.PATCH, r.DELETE — map กับ REST operations (CRUD), ใช้ path parameters (:id) และ wildcard (*path) สำหรับ flexible routing, r.Any match ทุก methods
Context (gin.Context)
*gin.Context เป็น core ของ Gin — ใช้ read request (params, query, body, headers), write response (JSON, HTML, file), pass data ระหว่าง middleware (c.Set/c.Get), control flow (c.Next, c.Abort)
Request Binding & Validation
Bind request body/query/params เข้า struct ด้วย c.ShouldBindJSON, c.ShouldBindQuery — ใช้ struct tags binding:"required,min=1,max=100" validate อัตโนมัติ, return error ถ้า validation fail
Middleware
Middleware คือ function ที่ run ก่อน/หลัง handler — r.Use(middleware) apply ทั้ง router, group.Use() apply เฉพาะ group, c.Next() ไป handler ถัดไป, c.Abort() หยุด chain
Built-in Middleware
gin.Logger()— log ทุก request (method, path, status, latency)gin.Recovery()— recover จาก panic, return 500 แทนที่จะ crash- ใช้ทั้งคู่เป็น default:
r := gin.Default()=gin.New()+ Logger + Recovery
Custom Middleware
เขียน middleware เอง — pattern: func MyMiddleware() gin.HandlerFunc { return func(c *gin.Context) { ... c.Next() ... } } ใช้สำหรับ auth, rate limiting, request ID, timing, CORS
Response Formats
c.JSON(200, gin.H{"key": "value"})— JSON response (ใช้บ่อยสุด)c.String,c.HTML,c.XML,c.File,c.Streamgin.Hเป็น shortcut สำหรับmap[string]any- Set headers ด้วย
c.Header("key", "value")
Error Handling
c.Error(err)— collect errors ใน context, handle รวมใน error middleware- Custom error types + centralized error handler middleware
c.AbortWithStatusJSON(400, gin.H{"error": msg})— return error ทันที
Path Parameters & Query Strings
- Path params —
r.GET("/users/:id"), อ่านด้วยc.Param("id") - Query strings —
/users?page=1&limit=10, อ่านด้วยc.Query("page"),c.DefaultQuery("limit", "20") - ใช้
c.ShouldBindQuery(&struct)bind query params เข้า struct
Authentication Middleware
Implement JWT/OAuth middleware — extract token จาก Authorization header, validate, set user info ใน context ด้วย c.Set("userID", id) handler อ่านด้วย c.Get("userID")
Rate Limiting
ใช้ middleware + token bucket หรือ sliding window algorithm — จำกัด requests per IP/user/endpoint, return 429 Too Many Requests เมื่อเกิน limit, ใช้ Redis เก็บ counters สำหรับ distributed systems
File Upload & Static Files
c.FormFile("file")— receive uploaded filec.SaveUploadedFile(file, dst)— save to diskr.Static("/assets", "./public")— serve static filesr.MaxMultipartMemory— limit upload size
Graceful Shutdown
ใช้ http.Server แทน r.Run() เพื่อ handle OS signals — srv.Shutdown(ctx) drain existing connections, ปิด cleanly ไม่ drop in-flight requests
Testing Handlers
ใช้ httptest.NewRecorder() + gin.CreateTestContext() — สร้าง request, เรียก handler, assert response status/body โดยไม่ต้อง start server จริง
Structured Logging
Replace default Logger middleware ด้วย structured logger (zerolog, zap) — log request ID, user ID, latency, errors เป็น JSON สำหรับ centralized log aggregation
CORS Configuration
ใช้ github.com/gin-contrib/cors middleware — กำหนด allowed origins, methods, headers สำหรับ cross-origin requests จาก frontend, config ต่าง environment (dev allow all, prod restrict)
Dependency Injection in Handlers
ส่ง dependencies (DB, cache, services) เข้า handler ผ่าน struct method หรือ closure — type Handler struct { db *sql.DB } แล้ว r.GET("/users", h.ListUsers) ทำให้ test ง่าย mock dependencies ได้
Performance Tuning
gin.SetMode(gin.ReleaseMode)— ปิด debug logging ใน production- Object pooling ด้วย
sync.Poolสำหรับ frequently allocated objects - Response compression ด้วย gzip middleware
- Connection pooling สำหรับ downstream services
API Versioning
จัดการ API versions ด้วย route groups — v1 := r.Group("/api/v1"), v2 := r.Group("/api/v2") แต่ละ version มี handlers แยก, share middleware ที่ common, deprecate versions เก่าอย่าง graceful
ปัญหาที่เจอบ่อย & วิธีแก้
Binding errors ไม่แสดง validation message ที่เข้าใจง่าย
ShouldBindJSON return error เป็น raw validator errors ที่อ่านยาก
สาเหตุ: go-playground/validator return validator.ValidationErrors ที่เป็น struct ซับซ้อน — ส่งกลับ client ตรงๆ ได้ข้อความแบบ Key: 'User.Name' Error:Field validation for 'Name' failed on the 'required' tag
วิธีแก้:
- เขียน error translator ที่แปลง
validator.ValidationErrorsเป็น map ของ field → readable message - สร้าง middleware ที่ format validation errors ก่อนส่ง response เป็น
{"errors": {"name": "ต้องกรอก", "email": "format ไม่ถูก"}} - Register custom validation messages ด้วย
validator.RegisterTranslation
Middleware execution order ทำงานไม่ตรงที่คิด
Code หลัง c.Next() ใน middleware แรก กลับ run ทีหลังสุด
สาเหตุ: c.Next() ทำงานแบบ stack (LIFO) — middleware A เรียก c.Next() → เข้า middleware B → เข้า handler → กลับมา code หลัง c.Next() ของ B → กลับมา code หลัง c.Next() ของ A
วิธีแก้:
- วาด execution flow ก่อน implement — code ก่อน
c.Next()= pre-handler, code หลังc.Next()= post-handler - ใช้ logging middleware debug ลำดับ: log ก่อนและหลัง
c.Next()ดู sequence - ถ้าต้องการ middleware ที่ run หลัง response (เช่น logging latency) ให้วาง code หลัง
c.Next()
Context value type assertion panic
c.Get("key") return interface{} — assert ผิด type จะ panic
สาเหตุ: c.Get() return (value interface{}, exists bool) ถ้าไม่ตรวจ exists แล้ว assert type ตรงๆ เช่น c.Get("userID").(int) จะ panic ถ้า key ไม่มีหรือ type ไม่ตรง
วิธีแก้:
- ใช้ comma-ok pattern:
val, exists := c.Get("userID"); if !exists { ... } - ใช้ helper methods:
c.GetString("key"),c.GetInt("key")ที่ return zero value ถ้าไม่มี - สร้าง typed helper functions สำหรับ values ที่ใช้บ่อย เช่น
func GetUserID(c *gin.Context) (int, error)
Goroutine ใช้ gin.Context หลัง request จบ — data race
ส่ง *gin.Context เข้า goroutine แล้วเจอ data race หรือ panic
สาเหตุ: gin.Context ถูก reuse ผ่าน sync.Pool — หลัง request จบ context จะถูก reset และใช้กับ request ใหม่ goroutine ที่ยังถือ reference เดิมจะอ่าน/เขียน data ของ request อื่น
วิธีแก้:
- ใช้
c.Copy()ก่อนส่งเข้า goroutine เสมอ:cCopy := c.Copy(); go func() { /* ใช้ cCopy */ }() - หรือ copy เฉพาะ values ที่ต้องการ:
userID := c.GetInt("userID"); go func() { /* ใช้ userID */ }() - อย่าเรียก
c.JSON()หรือ write response จาก goroutine — response ต้อง write ใน handler เท่านั้น
CORS preflight request ไม่ผ่าน
Frontend เรียก API แล้วเจอ CORS error ใน browser
สาเหตุ: Browser ส่ง OPTIONS preflight request ก่อน actual request — ถ้า CORS middleware อยู่หลัง auth middleware, preflight จะถูก reject เพราะไม่มี token
วิธีแก้:
- วาง CORS middleware ก่อน auth middleware เสมอ:
r.Use(cors.Default())ก่อนr.Use(authMiddleware()) - ใช้
github.com/gin-contrib/corsconfig ให้ครบ:AllowOrigins,AllowMethods,AllowHeaders - ตรวจ browser DevTools → Network tab → ดู preflight request และ response headers
Route conflicts — :id ชนกับ static path
/users/:id กับ /users/admin conflict กัน
สาเหตุ: Gin ใช้ radix tree routing — :id เป็น wildcard ที่ match ทุก string รวมถึง "admin" ทำให้ register ทั้งสอง routes ไม่ได้ หรือ route ผิดตัว
วิธีแก้:
- restructure URL: ใช้
/admin/usersแทน/users/adminหลีกเลี่ยง conflict กับ/users/:id - ใช้ route groups แยก patterns ให้ชัดเจน
- ถ้าจำเป็นต้องใช้ path เดียวกัน: handle ใน handler เดียวแล้วตรวจ param ว่าเป็น ID หรือ keyword