Gin Cheat Sheet
Overview
Gin is a high-performance HTTP web framework written in Go (Golang) that features a martini-like API with much better performance, up to 40 times faster thanks to its radix tree-based routing. It provides a robust set of middleware, JSON validation, route grouping, and error management capabilities while maintaining a clean and intuitive API. Gin is one of the most popular Go web frameworks with over 70,000 GitHub stars.
Gin uses httprouter internally, which provides fast HTTP routing using a radix tree structure. The framework supports middleware chaining, crash-free request handling with built-in recovery, JSON/XML/YAML rendering, and request binding and validation using struct tags. It is well-suited for building RESTful APIs, microservices, and web applications that require high throughput and low latency.
Installation
Setup
# Initialize Go module
mkdir my-api && cd my-api
go mod init my-api
# Install Gin
go get -u github.com/gin-gonic/gin
# Run
go run main.go
Minimal Server
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // Logger + Recovery middleware
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Hello, World!"})
})
r.Run(":8080") // Default: 0.0.0.0:8080
}
Routing
HTTP Methods
r.GET("/users", getUsers)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.PATCH("/users/:id", patchUser)
r.DELETE("/users/:id", deleteUser)
r.HEAD("/users", headUsers)
r.OPTIONS("/users", optionsUsers)
// Match any HTTP method
r.Any("/any", handleAny)
// Handle 404
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"error": "Not found"})
})
Path Parameters
// Named parameter
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
})
// Wildcard parameter
r.GET("/files/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
c.JSON(200, gin.H{"path": filepath})
})
Route Groups
// API versioning
v1 := r.Group("/api/v1")
{
v1.GET("/users", getUsers)
v1.POST("/users", createUser)
}
// With middleware
authorized := r.Group("/admin")
authorized.Use(AuthMiddleware())
{
authorized.GET("/dashboard", adminDashboard)
authorized.POST("/settings", updateSettings)
}
// Nested groups
api := r.Group("/api")
{
public := api.Group("/public")
{
public.GET("/info", getInfo)
}
private := api.Group("/private")
private.Use(AuthRequired())
{
private.GET("/profile", getProfile)
}
}
Request Handling
Query Parameters
r.GET("/search", func(c *gin.Context) {
query := c.Query("q") // Returns "" if not present
page := c.DefaultQuery("page", "1") // With default
limit, exists := c.GetQuery("limit") // Check existence
c.JSON(200, gin.H{
"query": query,
"page": page,
"limit": limit,
})
})
Request Binding and Validation
type CreateUserRequest struct {
Name string `json:"name" binding:"required,min=2,max=100"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"gte=0,lte=150"`
Password string `json:"password" binding:"required,min=8"`
}
r.POST("/users", func(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// req is validated and populated
c.JSON(http.StatusCreated, gin.H{"user": req.Name})
})
Binding Types
| Method | Content Type | Struct Tag |
|---|---|---|
ShouldBindJSON | application/json | json:"field" |
ShouldBindXML | application/xml | xml:"field" |
ShouldBindQuery | Query string | form:"field" |
ShouldBindUri | URI parameters | uri:"field" |
ShouldBind | Auto-detect | Multiple |
// Bind URI parameters
type UserURI struct {
ID string `uri:"id" binding:"required,uuid"`
}
r.GET("/users/:id", func(c *gin.Context) {
var uri UserURI
if err := c.ShouldBindUri(&uri); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{"id": uri.ID})
})
// Bind query parameters
type SearchQuery struct {
Q string `form:"q" binding:"required"`
Page int `form:"page,default=1" binding:"min=1"`
Limit int `form:"limit,default=20" binding:"min=1,max=100"`
}
Response Types
// JSON response
c.JSON(200, gin.H{"status": "ok"})
c.JSON(200, user) // Serialize struct
// XML
c.XML(200, gin.H{"status": "ok"})
// YAML
c.YAML(200, gin.H{"status": "ok"})
// String
c.String(200, "Hello %s", name)
// HTML template
c.HTML(200, "index.html", gin.H{"title": "Home"})
// Redirect
c.Redirect(http.StatusMovedPermanently, "/new-url")
// File
c.File("./path/to/file.pdf")
// Stream
c.Stream(func(w io.Writer) bool {
w.Write([]byte("data chunk"))
return false // false to stop
})
// Set headers
c.Header("X-Custom-Header", "value")
c.Header("Cache-Control", "no-cache")
Middleware
Built-in Middleware
// Default includes Logger and Recovery
r := gin.Default()
// Without middleware
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
Custom Middleware
func RequestTimer() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next() // Process request
duration := time.Since(start)
c.Header("X-Response-Time", duration.String())
}
}
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
return
}
user, err := validateToken(token)
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"error": "Invalid token"})
return
}
c.Set("user", user) // Store in context
c.Next()
}
}
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type,Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
// Apply middleware
r.Use(RequestTimer())
r.Use(CORSMiddleware())
Configuration
Gin Modes
# Set via environment variable
export GIN_MODE=release # release, debug, test
# Or in code
gin.SetMode(gin.ReleaseMode)
Custom Validators
import "github.com/go-playground/validator/v10"
func setupValidators(r *gin.Engine) {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("nowhitespace", func(fl validator.FieldLevel) bool {
return !strings.Contains(fl.Field().String(), " ")
})
}
}
type User struct {
Username string `json:"username" binding:"required,nowhitespace"`
}
Templates
r.LoadHTMLGlob("templates/*")
// or
r.LoadHTMLFiles("templates/index.html", "templates/about.html")
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"title": "My Site",
"users": users,
})
})
Advanced Usage
Graceful Shutdown
func main() {
r := gin.Default()
r.GET("/", handler)
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
}
File Upload
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
dst := filepath.Join("./uploads", file.Filename)
c.SaveUploadedFile(file, dst)
c.JSON(200, gin.H{"filename": file.Filename})
})
// Multiple files
r.POST("/upload-multi", func(c *gin.Context) {
form, _ := c.MultipartForm()
files := form.File["files"]
for _, file := range files {
c.SaveUploadedFile(file, filepath.Join("./uploads", file.Filename))
}
c.JSON(200, gin.H{"count": len(files)})
})
Testing
func TestGetUsers(t *testing.T) {
gin.SetMode(gin.TestMode)
r := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/users", nil)
r.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
var response map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &response)
assert.NotNil(t, response["users"])
}
Troubleshooting
| Problem | Solution |
|---|---|
404 Not Found on valid routes | Check route registration order; specific before wildcard |
| Middleware not executing | Verify Use() called before route registration |
| Binding fails silently | Use ShouldBind* (returns error) not Bind* (aborts) |
| CORS errors | Add CORS middleware before route handlers |
| Context deadline exceeded | Check handler timeout; use c.Request.Context() |
| High memory usage | Use gin.DisableConsoleColor() in production; stream large responses |
| Panic in handler | gin.Recovery() middleware catches panics; check logs |
| Slow response times | Profile with pprof; check database queries and middleware chain |