Skip to content

What it is

Gin is a high-performance HTTP web framework for Go with a martini-like API. It provides routing, middleware, JSON binding and validation on top of net/http, and is one of the most widely used Go web frameworks.

Gin gives you a router with path parameters and groups, a middleware chain, request binding into structs with validation tags, and helpers for JSON, XML and file responses. A Gin handler receives a *gin.Context carrying the request, response writer and per-request state.

Installation

go get -u github.com/gin-gonic/gin

Getting started

The smallest useful thing you can do with it, and what each part means.

A minimal JSON API
package main

import "github.com/gin-gonic/gin"

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

    r.GET("/books/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.JSON(200, gin.H{"id": id, "title": "Dune"})
    })

    r.Run(":8080")
}
gin.Default() attaches logging and panic recovery. c.Param reads a path segment, and c.JSON writes the status code and serialises the body in one call.
Binding and validating a request body
type CreateBook struct {
    Title  string `json:"title"  binding:"required,max=200"`
    Author string `json:"author" binding:"required"`
    Year   int    `json:"year"   binding:"gte=1400,lte=2100"`
}

r.POST("/books", func(c *gin.Context) {
    var body CreateBook
    if err := c.ShouldBindJSON(&body); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }
    c.JSON(201, body)
})
The binding tags run go-playground/validator, so malformed input is rejected before your handler logic runs. Use ShouldBindJSON rather than BindJSON — the latter writes a 400 itself and leaves you unable to shape the error.

Advanced usage

Where the library earns its place over a simpler alternative.

Middleware, route groups and graceful shutdown
func RequireAPIKey(key string) gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.GetHeader("X-API-Key") != key {
            c.AbortWithStatusJSON(401, gin.H{"error": "unauthorised"})
            return // Abort stops the chain; a bare return would not
        }
        c.Next()
    }
}

r := gin.New()
r.Use(gin.Recovery())

admin := r.Group("/admin", RequireAPIKey(os.Getenv("API_KEY")))
admin.GET("/stats", statsHandler)

srv := &http.Server{Addr: ":8080", Handler: r}
go srv.ListenAndServe()

quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx) // finish in-flight requests, then stop
Gin is just an http.Handler, so the standard library's graceful shutdown works unchanged. AbortWithStatusJSON is essential in middleware — without Abort, later handlers still run even after you have written a response.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Handler continues after middleware writes an error response
Middleware must call c.Abort() (or AbortWithStatusJSON) before returning. A plain return only exits the middleware function, not the handler chain.
EOF when binding a JSON body
The body has already been read — c.Request.Body is a stream that can only be consumed once. If you need it twice, read it with io.ReadAll and restore it via c.Request.Body = io.NopCloser(bytes.NewReader(data)).

Best practices

  • Use ShouldBind* rather than Bind*, so you control the error response instead of Gin writing a bare 400.
  • Set gin.SetMode(gin.ReleaseMode) in production — debug mode logs every route and adds overhead.
  • Always call c.Abort() or AbortWithStatusJSON in middleware that rejects a request; returning alone does not stop the chain.
  • Wrap the router in an http.Server so you can set ReadTimeout, WriteTimeout and shut down gracefully.

Background

Why it exists, and what it was reacting to.

Gin was created in 2014 by Manuel Martínez-Almeida, who wanted Martini's ergonomics without its reflection overhead. By using a radix-tree router and avoiding reflection on the hot path, Gin achieved dramatically better throughput and quickly became the default choice for Go APIs.