Skip to content

Commit ad715ad

Browse files
appleboyclaude
andcommitted
docs(en): improve 15 additional low-quality doc pages
- Add introductory prose and runnable code to binding, routing, server-config, logging, and middleware pages - Add "Test it" sections with curl commands and expected output - Add "See also" cross-references to related documentation - Replace GitHub issue links with self-contained explanations - Fix heading levels from h3 to h2 for consistency - Make all code examples complete with package and imports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c79ae55 commit ad715ad

16 files changed

+489
-193
lines changed

src/content/docs/en/docs/binding/bind-body-into-different-structs.md

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,20 @@ sidebar:
44
order: 13
55
---
66

7-
The normal methods for binding request body consumes `c.Request.Body` and they
8-
cannot be called multiple times.
7+
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.
8+
9+
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.
910

1011
```go
12+
package main
13+
14+
import (
15+
"net/http"
16+
17+
"github.com/gin-gonic/gin"
18+
"github.com/gin-gonic/gin/binding"
19+
)
20+
1121
type formA struct {
1222
Foo string `json:"foo" xml:"foo" binding:"required"`
1323
}
@@ -16,46 +26,51 @@ type formB struct {
1626
Bar string `json:"bar" xml:"bar" binding:"required"`
1727
}
1828

19-
func SomeHandler(c *gin.Context) {
20-
objA := formA{}
21-
objB := formB{}
22-
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.
23-
if errA := c.ShouldBind(&objA); errA == nil {
24-
c.String(http.StatusOK, `the body should be formA`)
25-
// Always an error is occurred by this because c.Request.Body is EOF now.
26-
} else if errB := c.ShouldBind(&objB); errB == nil {
27-
c.String(http.StatusOK, `the body should be formB`)
28-
} else {
29-
...
30-
}
29+
func main() {
30+
router := gin.Default()
31+
32+
router.POST("/bind", func(c *gin.Context) {
33+
objA := formA{}
34+
objB := formB{}
35+
// This reads c.Request.Body and stores the result into the context.
36+
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
37+
c.JSON(http.StatusOK, gin.H{"message": "matched formA", "foo": objA.Foo})
38+
return
39+
}
40+
// At this time, it reuses body stored in the context.
41+
if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
42+
c.JSON(http.StatusOK, gin.H{"message": "matched formB", "bar": objB.Bar})
43+
return
44+
}
45+
46+
c.JSON(http.StatusBadRequest, gin.H{"error": "request body did not match any known format"})
47+
})
48+
49+
router.Run(":8080")
3150
}
3251
```
3352

34-
For this, you can use `c.ShouldBindBodyWith`.
53+
## Test it
3554

36-
```go
37-
func SomeHandler(c *gin.Context) {
38-
objA := formA{}
39-
objB := formB{}
40-
// This reads c.Request.Body and stores the result into the context.
41-
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
42-
c.String(http.StatusOK, `the body should be formA`)
43-
// At this time, it reuses body stored in the context.
44-
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
45-
c.String(http.StatusOK, `the body should be formB JSON`)
46-
// And it can accepts other formats
47-
} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
48-
c.String(http.StatusOK, `the body should be formB XML`)
49-
} else {
50-
...
51-
}
52-
}
55+
```sh
56+
# Body matches formA
57+
curl -X POST http://localhost:8080/bind \
58+
-H "Content-Type: application/json" \
59+
-d '{"foo":"hello"}'
60+
# Output: {"foo":"hello","message":"matched formA"}
61+
62+
# Body matches formB
63+
curl -X POST http://localhost:8080/bind \
64+
-H "Content-Type: application/json" \
65+
-d '{"bar":"world"}'
66+
# Output: {"bar":"world","message":"matched formB"}
5367
```
5468

55-
* `c.ShouldBindBodyWith` stores body into the context before binding. This has
56-
a slight impact to performance, so you should not use this method if you only need to bind once.
57-
* This feature is only needed for some formats -- `JSON`, `XML`, `MsgPack`,
58-
`ProtoBuf`. For other formats, `Query`, `Form`, `FormPost`, `FormMultipart`,
59-
can be called by `c.ShouldBind()` multiple times without any damage to
60-
performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
69+
:::note
70+
`c.ShouldBindBodyWith` stores the body in the context before binding. This has a slight performance impact, so only use it when you need to bind the body more than once. For formats that do not read the body — such as `Query`, `Form`, `FormPost`, `FormMultipart` — you can call `c.ShouldBind()` multiple times without issue.
71+
:::
72+
73+
## See also
6174

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

src/content/docs/en/docs/binding/custom-validators.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ sidebar:
44
order: 2
55
---
66

7-
It is also possible to register custom validators. See the [example code](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations).
7+
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.
8+
9+
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.
810

911
```go
1012
package main
@@ -56,13 +58,23 @@ func getBookable(c *gin.Context) {
5658
}
5759
```
5860

61+
## Test it
62+
5963
```sh
60-
$ curl "localhost:8085/bookable?check_in=2118-04-16&check_out=2118-04-17"
61-
{"message":"Booking dates are valid!"}
64+
# Both dates are in the future and check_out > check_in
65+
curl "http://localhost:8085/bookable?check_in=2118-04-16&check_out=2118-04-17"
66+
# Output: {"message":"Booking dates are valid!"}
6267

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

67-
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
68-
See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more.
73+
:::tip
74+
You can also register [struct-level validations](https://github.com/go-playground/validator/releases/tag/v8.7) for cross-field rules that go beyond single-field checks. See the [struct-lvl-validation example](https://github.com/gin-gonic/examples/tree/master/struct-lvl-validations) to learn more.
75+
:::
76+
77+
## See also
78+
79+
- [Binding and validation](/en/docs/binding/binding-and-validation/)
80+
- [Bind default values](/en/docs/binding/bind-default-values/)

src/content/docs/en/docs/binding/multipart-urlencoded-binding.md

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ sidebar:
44
order: 11
55
---
66

7+
`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.
8+
9+
This is commonly used for login forms, registration pages, or any HTML form submission.
10+
711
```go
812
package main
913

1014
import (
15+
"net/http"
16+
1117
"github.com/gin-gonic/gin"
1218
)
1319

@@ -18,25 +24,51 @@ type LoginForm struct {
1824

1925
func main() {
2026
router := gin.Default()
27+
2128
router.POST("/login", func(c *gin.Context) {
22-
// you can bind multipart form with explicit binding declaration:
23-
// c.ShouldBindWith(&form, binding.Form)
24-
// or you can simply use autobinding with ShouldBind method:
2529
var form LoginForm
26-
// in this case proper binding will be automatically selected
27-
if c.ShouldBind(&form) == nil {
28-
if form.User == "user" && form.Password == "password" {
29-
c.JSON(200, gin.H{"status": "you are logged in"})
30-
} else {
31-
c.JSON(401, gin.H{"status": "unauthorized"})
32-
}
30+
// ShouldBind automatically selects the right binding based on Content-Type
31+
if err := c.ShouldBind(&form); err != nil {
32+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
33+
return
34+
}
35+
36+
if form.User == "user" && form.Password == "password" {
37+
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
38+
} else {
39+
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
3340
}
3441
})
42+
3543
router.Run(":8080")
3644
}
3745
```
3846

39-
Test it with:
47+
## Test it
48+
4049
```sh
41-
$ curl -v --form user=user --form password=password http://localhost:8080/login
50+
# Multipart form
51+
curl -X POST http://localhost:8080/login \
52+
-F "user=user" -F "password=password"
53+
# Output: {"status":"you are logged in"}
54+
55+
# URL-encoded form
56+
curl -X POST http://localhost:8080/login \
57+
-d "user=user&password=password"
58+
# Output: {"status":"you are logged in"}
59+
60+
# Wrong credentials
61+
curl -X POST http://localhost:8080/login \
62+
-d "user=wrong&password=wrong"
63+
# Output: {"status":"unauthorized"}
64+
65+
# Missing required field
66+
curl -X POST http://localhost:8080/login \
67+
-d "user=user"
68+
# Output: {"error":"Key: 'LoginForm.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
4269
```
70+
71+
## See also
72+
73+
- [Binding and validation](/en/docs/binding/binding-and-validation/)
74+
- [Bind html checkboxes](/en/docs/binding/bind-html-checkbox/)

src/content/docs/en/docs/binding/only-bind-query-string.md

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ sidebar:
44
order: 3
55
---
66

7-
`ShouldBindQuery` function only binds the query params and not the post data. See the [detail information](https://github.com/gin-gonic/gin/issues/742#issuecomment-315953017).
7+
`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.
8+
9+
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.
810

911
```go
1012
package main
1113

1214
import (
13-
"log"
15+
"net/http"
1416

1517
"github.com/gin-gonic/gin"
1618
)
@@ -28,11 +30,32 @@ func main() {
2830

2931
func startPage(c *gin.Context) {
3032
var person Person
31-
if c.ShouldBindQuery(&person) == nil {
32-
log.Println("====== Only Bind By Query String ======")
33-
log.Println(person.Name)
34-
log.Println(person.Address)
33+
if err := c.ShouldBindQuery(&person); err != nil {
34+
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
35+
return
3536
}
36-
c.String(200, "Success")
37+
38+
c.JSON(http.StatusOK, gin.H{
39+
"name": person.Name,
40+
"address": person.Address,
41+
})
3742
}
3843
```
44+
45+
## Test it
46+
47+
```sh
48+
# GET with query parameters
49+
curl "http://localhost:8085/testing?name=appleboy&address=xyz"
50+
# Output: {"address":"xyz","name":"appleboy"}
51+
52+
# POST with query parameters -- body is ignored, only query is bound
53+
curl -X POST "http://localhost:8085/testing?name=appleboy&address=xyz" \
54+
-d "name=ignored&address=ignored"
55+
# Output: {"address":"xyz","name":"appleboy"}
56+
```
57+
58+
## See also
59+
60+
- [Bind query string or post data](/en/docs/binding/bind-query-or-post/)
61+
- [Binding and validation](/en/docs/binding/binding-and-validation/)

src/content/docs/en/docs/logging/define-format-for-the-log-of-routes.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ sidebar:
44
order: 6
55
---
66

7-
The default log of routes is:
7+
When Gin starts, it prints all registered routes in debug mode. The default format looks like this:
8+
89
```
910
[GIN-debug] POST /foo --> main.main.func1 (3 handlers)
1011
[GIN-debug] GET /bar --> main.main.func2 (3 handlers)
1112
[GIN-debug] GET /status --> main.main.func3 (3 handlers)
1213
```
1314

14-
If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`.
15-
In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.
15+
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.
16+
1617
```go
18+
package main
19+
1720
import (
1821
"log"
1922
"net/http"
@@ -23,6 +26,7 @@ import (
2326

2427
func main() {
2528
router := gin.Default()
29+
2630
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
2731
log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
2832
}
@@ -39,7 +43,19 @@ func main() {
3943
c.JSON(http.StatusOK, "ok")
4044
})
4145

42-
// Listen and Server in http://0.0.0.0:8080
43-
router.Run()
46+
router.Run(":8080")
4447
}
4548
```
49+
50+
When the server starts, instead of the default `[GIN-debug]` lines, you will see:
51+
52+
```
53+
endpoint POST /foo main.main.func2 3
54+
endpoint GET /bar main.main.func3 3
55+
endpoint GET /status main.main.func4 3
56+
```
57+
58+
## See also
59+
60+
- [Custom log format](/en/docs/logging/custom-log-format/)
61+
- [Skip logging](/en/docs/logging/skip-logging/)

0 commit comments

Comments
 (0)