gin

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Gin Core Knowledge

Gin核心知识

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
gin
for comprehensive documentation.
深度知识:使用
mcp__documentation__fetch_docs
工具,指定technology为
gin
以获取完整文档。

Basic Setup

基础配置

go
package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    r := gin.Default()  // Logger and Recovery middleware

    r.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello, World!")
    })

    r.Run(":8080")
}
go
package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main() {
    r := gin.Default()  // Logger and Recovery middleware

    r.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello, World!")
    })

    r.Run(":8080")
}

Routing

路由

Basic Routes

基础路由

go
r := gin.Default()

r.GET("/users", listUsers)
r.GET("/users/:id", getUser)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.DELETE("/users/:id", deleteUser)

// Any HTTP method
r.Any("/any", handleAny)

// NoRoute handler
r.NoRoute(func(c *gin.Context) {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
})
go
r := gin.Default()

r.GET("/users", listUsers)
r.GET("/users/:id", getUser)
r.POST("/users", createUser)
r.PUT("/users/:id", updateUser)
r.DELETE("/users/:id", deleteUser)

// Any HTTP method
r.Any("/any", handleAny)

// NoRoute handler
r.NoRoute(func(c *gin.Context) {
    c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
})

Path Parameters

路径参数

go
// Single parameter
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"id": id})
})

// Wildcard
r.GET("/files/*filepath", func(c *gin.Context) {
    filepath := c.Param("filepath")
    c.String(http.StatusOK, "File: %s", filepath)
})
go
// Single parameter
r.GET("/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(http.StatusOK, gin.H{"id": id})
})

// Wildcard
r.GET("/files/*filepath", func(c *gin.Context) {
    filepath := c.Param("filepath")
    c.String(http.StatusOK, "File: %s", filepath)
})

Route Groups

路由分组

go
api := r.Group("/api")
{
    v1 := api.Group("/v1")
    {
        v1.GET("/users", listUsersV1)
        v1.POST("/users", createUserV1)
    }

    v2 := api.Group("/v2")
    {
        v2.GET("/users", listUsersV2)
        v2.POST("/users", createUserV2)
    }
}
go
api := r.Group("/api")
{
    v1 := api.Group("/v1")
    {
        v1.GET("/users", listUsersV1)
        v1.POST("/users", createUserV1)
    }

    v2 := api.Group("/v2")
    {
        v2.GET("/users", listUsersV2)
        v2.POST("/users", createUserV2)
    }
}

Request Binding

请求绑定

JSON Binding

JSON绑定

go
type CreateUserRequest struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"gte=0,lte=130"`
}

func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, gin.H{
        "name":  req.Name,
        "email": req.Email,
    })
}
go
type CreateUserRequest struct {
    Name  string `json:"name" binding:"required"`
    Email string `json:"email" binding:"required,email"`
    Age   int    `json:"age" binding:"gte=0,lte=130"`
}

func createUser(c *gin.Context) {
    var req CreateUserRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, gin.H{
        "name":  req.Name,
        "email": req.Email,
    })
}

Query Binding

查询参数绑定

go
type ListUsersQuery struct {
    Page    int    `form:"page" binding:"gte=1"`
    PerPage int    `form:"per_page" binding:"gte=1,lte=100"`
    Sort    string `form:"sort"`
}

func listUsers(c *gin.Context) {
    var query ListUsersQuery
    query.Page = 1
    query.PerPage = 20

    if err := c.ShouldBindQuery(&query); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "page":     query.Page,
        "per_page": query.PerPage,
    })
}
go
type ListUsersQuery struct {
    Page    int    `form:"page" binding:"gte=1"`
    PerPage int    `form:"per_page" binding:"gte=1,lte=100"`
    Sort    string `form:"sort"`
}

func listUsers(c *gin.Context) {
    var query ListUsersQuery
    query.Page = 1
    query.PerPage = 20

    if err := c.ShouldBindQuery(&query); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "page":     query.Page,
        "per_page": query.PerPage,
    })
}

Form Binding

表单绑定

go
type LoginForm struct {
    Username string `form:"username" binding:"required"`
    Password string `form:"password" binding:"required"`
}

func login(c *gin.Context) {
    var form LoginForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"username": form.Username})
}
go
type LoginForm struct {
    Username string `form:"username" binding:"required"`
    Password string `form:"password" binding:"required"`
}

func login(c *gin.Context) {
    var form LoginForm
    if err := c.ShouldBind(&form); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"username": form.Username})
}

URI Binding

URI绑定

go
type UserURI struct {
    ID uint `uri:"id" binding:"required"`
}

func getUser(c *gin.Context) {
    var uri UserURI
    if err := c.ShouldBindUri(&uri); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"id": uri.ID})
}
go
type UserURI struct {
    ID uint `uri:"id" binding:"required"`
}

func getUser(c *gin.Context) {
    var uri UserURI
    if err := c.ShouldBindUri(&uri); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{"id": uri.ID})
}

Validation

校验

Custom Validator

自定义校验器

go
import "github.com/go-playground/validator/v10"

var validateUsername validator.Func = func(fl validator.FieldLevel) bool {
    username := fl.Field().String()
    return len(username) >= 3 && len(username) <= 20
}

func main() {
    r := gin.Default()

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("username", validateUsername)
    }

    r.Run(":8080")
}

type RegisterRequest struct {
    Username string `json:"username" binding:"required,username"`
    Email    string `json:"email" binding:"required,email"`
}
go
import "github.com/go-playground/validator/v10"

var validateUsername validator.Func = func(fl validator.FieldLevel) bool {
    username := fl.Field().String()
    return len(username) >= 3 && len(username) <= 20
}

func main() {
    r := gin.Default()

    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        v.RegisterValidation("username", validateUsername)
    }

    r.Run(":8080")
}

type RegisterRequest struct {
    Username string `json:"username" binding:"required,username"`
    Email    string `json:"email" binding:"required,email"`
}

Middleware

中间件

Built-in Middleware

内置中间件

go
r := gin.New()

r.Use(gin.Logger())
r.Use(gin.Recovery())

r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
    return fmt.Sprintf("%s - [%s] %s %s %d %s\n",
        param.ClientIP,
        param.TimeStamp.Format(time.RFC1123),
        param.Method,
        param.Path,
        param.StatusCode,
        param.Latency,
    )
}))
go
r := gin.New()

r.Use(gin.Logger())
r.Use(gin.Recovery())

r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
    return fmt.Sprintf("%s - [%s] %s %s %d %s\n",
        param.ClientIP,
        param.TimeStamp.Format(time.RFC1123),
        param.Method,
        param.Path,
        param.StatusCode,
        param.Latency,
    )
}))

Custom Middleware

自定义中间件

go
func TimingMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        duration := time.Since(start)
        c.Header("X-Response-Time", duration.String())
    }
}

func main() {
    r := gin.Default()
    r.Use(TimingMiddleware())
}
go
func TimingMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        duration := time.Since(start)
        c.Header("X-Response-Time", duration.String())
    }
}

func main() {
    r := gin.Default()
    r.Use(TimingMiddleware())
}

Authentication Middleware

认证中间件

go
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")

        if token == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
            c.Abort()
            return
        }

        if !strings.HasPrefix(token, "Bearer ") {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid format"})
            c.Abort()
            return
        }

        tokenString := token[7:]
        user, err := validateToken(tokenString)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            c.Abort()
            return
        }

        c.Set("user", user)
        c.Next()
    }
}

protected := r.Group("/api")
protected.Use(AuthMiddleware())
{
    protected.GET("/me", getMe)
}

func getMe(c *gin.Context) {
    user, _ := c.Get("user")
    c.JSON(http.StatusOK, user)
}
go
func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        token := c.GetHeader("Authorization")

        if token == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "missing token"})
            c.Abort()
            return
        }

        if !strings.HasPrefix(token, "Bearer ") {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid format"})
            c.Abort()
            return
        }

        tokenString := token[7:]
        user, err := validateToken(tokenString)
        if err != nil {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            c.Abort()
            return
        }

        c.Set("user", user)
        c.Next()
    }
}

protected := r.Group("/api")
protected.Use(AuthMiddleware())
{
    protected.GET("/me", getMe)
}

func getMe(c *gin.Context) {
    user, _ := c.Get("user")
    c.JSON(http.StatusOK, user)
}

CORS Middleware

CORS中间件

go
import "github.com/gin-contrib/cors"

func main() {
    r := gin.Default()

    r.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"https://example.com"},
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
        AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
        AllowCredentials: true,
        MaxAge:           12 * time.Hour,
    }))

    r.Run(":8080")
}
go
import "github.com/gin-contrib/cors"

func main() {
    r := gin.Default()

    r.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"https://example.com"},
        AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
        AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
        AllowCredentials: true,
        MaxAge:           12 * time.Hour,
    }))

    r.Run(":8080")
}

Response Rendering

响应渲染

JSON Response

JSON响应

go
func getUser(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "id":   1,
        "name": "Alice",
    })
}

type User struct {
    ID   uint   `json:"id"`
    Name string `json:"name"`
}

func getUser(c *gin.Context) {
    user := User{ID: 1, Name: "Alice"}
    c.JSON(http.StatusOK, user)
}
go
func getUser(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "id":   1,
        "name": "Alice",
    })
}

type User struct {
    ID   uint   `json:"id"`
    Name string `json:"name"`
}

func getUser(c *gin.Context) {
    user := User{ID: 1, Name: "Alice"}
    c.JSON(http.StatusOK, user)
}

Other Formats

其他格式

go
c.XML(http.StatusOK, gin.H{"message": "hello"})
c.YAML(http.StatusOK, gin.H{"message": "hello"})
c.String(http.StatusOK, "Hello, %s!", name)
c.Redirect(http.StatusMovedPermanently, "https://example.com")
c.File("./static/file.txt")
go
c.XML(http.StatusOK, gin.H{"message": "hello"})
c.YAML(http.StatusOK, gin.H{"message": "hello"})
c.String(http.StatusOK, "Hello, %s!", name)
c.Redirect(http.StatusMovedPermanently, "https://example.com")
c.File("./static/file.txt")

Production Readiness

生产环境就绪

Configuration

配置

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

    r := gin.New()
    r.Use(gin.Recovery())
    r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
        Formatter: jsonLogFormatter,
        Output:    os.Stdout,
    }))

    r.Run(":8080")
}

func jsonLogFormatter(param gin.LogFormatterParams) string {
    log := map[string]interface{}{
        "timestamp": param.TimeStamp.Format(time.RFC3339),
        "status":    param.StatusCode,
        "latency":   param.Latency.Milliseconds(),
        "method":    param.Method,
        "path":      param.Path,
    }
    b, _ := json.Marshal(log)
    return string(b) + "\n"
}
go
func main() {
    gin.SetMode(gin.ReleaseMode)

    r := gin.New()
    r.Use(gin.Recovery())
    r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
        Formatter: jsonLogFormatter,
        Output:    os.Stdout,
    }))

    r.Run(":8080")
}

func jsonLogFormatter(param gin.LogFormatterParams) string {
    log := map[string]interface{}{
        "timestamp": param.TimeStamp.Format(time.RFC3339),
        "status":    param.StatusCode,
        "latency":   param.Latency.Milliseconds(),
        "method":    param.Method,
        "path":      param.Path,
    }
    b, _ := json.Marshal(log)
    return string(b) + "\n"
}

Health Checks

健康检查

go
func healthHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"status": "healthy"})
}

func readyHandler(db *sql.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
        defer cancel()

        if err := db.PingContext(ctx); err != nil {
            c.JSON(http.StatusServiceUnavailable, gin.H{
                "status":   "not ready",
                "database": "disconnected",
            })
            return
        }

        c.JSON(http.StatusOK, gin.H{
            "status":   "ready",
            "database": "connected",
        })
    }
}
go
func healthHandler(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"status": "healthy"})
}

func readyHandler(db *sql.DB) gin.HandlerFunc {
    return func(c *gin.Context) {
        ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
        defer cancel()

        if err := db.PingContext(ctx); err != nil {
            c.JSON(http.StatusServiceUnavailable, gin.H{
                "status":   "not ready",
                "database": "disconnected",
            })
            return
        }

        c.JSON(http.StatusOK, gin.H{
            "status":   "ready",
            "database": "connected",
        })
    }
}

Graceful Shutdown

优雅停机

go
func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello")
    })

    srv := &http.Server{
        Addr:    ":8080",
        Handler: r,
    }

    go func() {
        if err := srv.ListenAndServe(); 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(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown:", err)
    }
}
go
func main() {
    r := gin.Default()
    r.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello")
    })

    srv := &http.Server{
        Addr:    ":8080",
        Handler: r,
    }

    go func() {
        if err := srv.ListenAndServe(); 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(), 30*time.Second)
    defer cancel()

    if err := srv.Shutdown(ctx); err != nil {
        log.Fatal("Server forced to shutdown:", err)
    }
}

Checklist

检查清单

  • gin.ReleaseMode for production
  • CORS configured
  • Authentication middleware
  • Request validation
  • Structured JSON logging
  • Health/readiness endpoints
  • Graceful shutdown
  • Rate limiting
  • 使用gin.ReleaseMode运行生产环境
  • 配置CORS
  • 实现认证中间件
  • 启用请求校验
  • 结构化JSON日志
  • 健康/就绪检查端点
  • 优雅停机
  • 限流

When NOT to Use This Skill

不适用场景

  • Echo projects - Echo has different middleware patterns
  • Fiber projects - Fiber is Express-like with different API
  • Chi projects - Chi is stdlib-compatible router
  • gRPC services - Use gRPC framework directly
  • Minimal stdlib apps - Gin adds dependency overhead
  • Echo项目:Echo拥有不同的中间件模式
  • Fiber项目:Fiber类Express框架,API不同
  • Chi项目:Chi是兼容标准库的路由
  • gRPC服务:直接使用gRPC框架
  • 轻量标准库应用:Gin会增加依赖开销

Anti-Patterns

反模式

Anti-PatternWhy It's BadSolution
Using
gin.Default()
without knowing
Includes logger/recovery you may not wantUse
gin.New()
and add middleware explicitly
Not checking binding errorsInvalid data processedAlways check
ShouldBindJSON()
error
Using
c.String()
for JSON
Manual serialization error-proneUse
c.JSON()
instead
Global variables for configHard to testPass config via context or dependency injection
Missing validation tagsNo input validationUse
binding:"required,email"
tags
Not setting gin.ReleaseModeDebug logs in productionSet
gin.SetMode(gin.ReleaseMode)
反模式问题所在解决方案
盲目使用
gin.Default()
包含可能不需要的日志/恢复中间件使用
gin.New()
并显式添加所需中间件
不检查绑定错误会处理无效数据务必检查
ShouldBindJSON()
返回的错误
使用
c.String()
返回JSON
手动序列化容易出错使用
c.JSON()
替代
使用全局变量存储配置难以测试通过上下文或依赖注入传递配置
缺失校验标签没有输入校验使用
binding:"required,email"
这类标签
未设置gin.ReleaseMode生产环境输出调试日志设置
gin.SetMode(gin.ReleaseMode)

Quick Troubleshooting

快速故障排查

ProblemDiagnosisFix
Route not found (404)Route not registeredCheck router setup and method
Binding fails silentlyNot checking errorCheck
ShouldBindJSON()
return value
CORS errorsNot configuredUse
gin-contrib/cors
middleware
Middleware not executingWrong orderPlace middleware before routes
Panic in handlerNo recovery middlewareUse
gin.Recovery()
Slow JSON serializationLarge responseUse
c.Stream()
or pagination
问题诊断解决方法
路由未找到(404)路由未注册检查路由配置和请求方法
绑定失败无提示未检查错误检查
ShouldBindJSON()
的返回值
CORS错误未配置CORS使用
gin-contrib/cors
中间件
中间件未执行顺序错误将中间件添加在路由之前
处理器发生panic未使用恢复中间件使用
gin.Recovery()
JSON序列化缓慢响应数据过大使用
c.Stream()
或分页

Reference Documentation

参考文档

  • Routing
  • Binding
  • Middleware
  • 路由
  • 绑定
  • 中间件