Ir al contenido

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

MethodContent TypeStruct Tag
ShouldBindJSONapplication/jsonjson:"field"
ShouldBindXMLapplication/xmlxml:"field"
ShouldBindQueryQuery stringform:"field"
ShouldBindUriURI parametersuri:"field"
ShouldBindAuto-detectMultiple
// 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

ProblemSolution
404 Not Found on valid routesCheck route registration order; specific before wildcard
Middleware not executingVerify Use() called before route registration
Binding fails silentlyUse ShouldBind* (returns error) not Bind* (aborts)
CORS errorsAdd CORS middleware before route handlers
Context deadline exceededCheck handler timeout; use c.Request.Context()
High memory usageUse gin.DisableConsoleColor() in production; stream large responses
Panic in handlergin.Recovery() middleware catches panics; check logs
Slow response timesProfile with pprof; check database queries and middleware chain