Gin Routing Guide

Gin framework routing patterns: groups, parameters, query strings, wildcard routes, custom validators, and handler organization.

1. Basic Routes & HTTP Methods

package main

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

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

    r.GET("/ping", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"message": "pong"})
    })

    r.POST("/articles", createArticle)
    r.PUT("/articles/:id", updateArticle)
    r.PATCH("/articles/:id", patchArticle)
    r.DELETE("/articles/:id", deleteArticle)
    r.HEAD("/articles/:id", headArticle)
    r.OPTIONS("/articles", optionsArticle)

    // Match any method
    r.Any("/any", func(c *gin.Context) {
        c.String(http.StatusOK, "method: %s", c.Request.Method)
    })

    r.Run(":8080")
}

2. Route Groups

func SetupRoutes(r *gin.Engine) {
    // API v1 group
    v1 := r.Group("/api/v1")
    {
        articles := v1.Group("/articles")
        {
            articles.GET("", ListArticles)
            articles.POST("", CreateArticle)
            articles.GET("/:id", GetArticle)
            articles.PUT("/:id", UpdateArticle)
            articles.DELETE("/:id", DeleteArticle)
        }

        users := v1.Group("/users")
        users.Use(AuthMiddleware()) // apply middleware to group
        {
            users.GET("/me", GetMe)
            users.PUT("/me", UpdateMe)
        }
    }

    // Admin group with auth
    admin := r.Group("/admin", AuthMiddleware(), AdminOnlyMiddleware())
    {
        admin.GET("/stats", GetStats)
        admin.GET("/users", AdminListUsers)
    }
}

3. Path Parameters & Query Strings

// Path parameters
r.GET("/articles/:id", func(c *gin.Context) {
    id := c.Param("id")
    c.JSON(200, gin.H{"id": id})
})

// Wildcard โ€” matches /files/images/logo.png
r.GET("/files/*filepath", func(c *gin.Context) {
    path := c.Param("filepath") // "/images/logo.png"
    c.String(200, "file: %s", path)
})

// Query parameters
// GET /search?q=golang&page=2&limit=20&sort=date
r.GET("/search", func(c *gin.Context) {
    q := c.Query("q")                       // "" if missing
    page := c.DefaultQuery("page", "1")     // "1" if missing
    limit := c.DefaultQuery("limit", "10")

    // Bind all query params to struct
    var params SearchParams
    if err := c.ShouldBindQuery(&params); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    c.JSON(200, gin.H{"q": q, "page": page})
})

4. Binding & Validation

type CreateArticleRequest struct {
    Title   string   `json:"title"   binding:"required,min=5,max=300"`
    Content string   `json:"content" binding:"required,min=50"`
    Tags    []string `json:"tags"    binding:"dive,min=1,max=50"`
    Status  string   `json:"status"  binding:"omitempty,oneof=draft published"`
}

func CreateArticle(c *gin.Context) {
    var req CreateArticleRequest

    // ShouldBindJSON โ€” returns error, doesn't abort
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusUnprocessableEntity, gin.H{
            "error": "Validation failed",
            "details": err.Error(),
        })
        return
    }

    article, err := articleService.Create(req)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, article)
}

5. Custom Validators

package main

import (
    "github.com/go-playground/validator/v10"
    "github.com/gin-gonic/gin/binding"
)

func init() {
    if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
        // Register custom tag
        v.RegisterValidation("slug", validateSlug)
        v.RegisterValidation("phone", validatePhone)
    }
}

func validateSlug(fl validator.FieldLevel) bool {
    import regexp
    re := regexp.MustCompile(`^[a-z0-9]+(?:-[a-z0-9]+)*$`)
    return re.MatchString(fl.Field().String())
}

// Usage in struct tag
type ArticleRequest struct {
    Slug string `json:"slug" binding:"required,slug"`
}

// Handler groups with common logic
type ArticleHandler struct {
    service ArticleService
}

func NewArticleHandler(s ArticleService) *ArticleHandler {
    return &ArticleHandler{service: s}
}

func (h *ArticleHandler) Register(r *gin.RouterGroup) {
    r.GET("",     h.List)
    r.POST("",    h.Create)
    r.GET("/:id", h.Get)
    r.PUT("/:id", h.Update)
}

6. gin.Context Response Methods

MethodContent-TypeUse When
c.JSON(code, obj)application/jsonJSON API responses
c.String(code, fmt, ...)text/plainSimple text
c.HTML(code, tmpl, data)text/htmlTemplate rendering
c.XML(code, obj)application/xmlXML responses
c.Data(code, ct, data)customRaw bytes
c.File(path)โ€”Serve file
c.Redirect(code, url)โ€”HTTP redirect
c.AbortWithStatus(code)โ€”Stop middleware chain