go 69 lines · 9 steps

Turning Gin validation errors into JSON

A Gin handler binds and validates a request, then maps validator failures into structured, per-field error messages.

Explained by highlit
1package api
2 
3import (
4 "errors"
5 "net/http"
6 
7 "github.com/gin-gonic/gin"
8 "github.com/go-playground/validator/v10"
9)
10 
11type CreateUserRequest struct {
12 Email string `json:"email" binding:"required,email"`
13 Username string `json:"username" binding:"required,min=3,max=32,alphanum"`
14 Password string `json:"password" binding:"required,min=8"`
15 Age int `json:"age" binding:"required,gte=13,lte=120"`
16}
17 
18type FieldError struct {
19 Field string `json:"field"`
20 Message string `json:"message"`
21}
22 
23func (h *Handler) CreateUser(c *gin.Context) {
24 var req CreateUserRequest
25 if err := c.ShouldBindJSON(&req); err != nil {
26 var verrs validator.ValidationErrors
27 if errors.As(err, &verrs) {
28 fields := make([]FieldError, 0, len(verrs))
29 for _, fe := range verrs {
30 fields = append(fields, FieldError{
31 Field: fe.Field(),
32 Message: messageFor(fe),
33 })
34 }
35 c.JSON(http.StatusUnprocessableEntity, gin.H{"errors": fields})
36 return
37 }
38 c.JSON(http.StatusBadRequest, gin.H{"error": "malformed JSON body"})
39 return
40 }
41 
42 user, err := h.users.Create(c.Request.Context(), req.Email, req.Username, req.Password, req.Age)
43 if err != nil {
44 c.JSON(http.StatusInternalServerError, gin.H{"error": "could not create user"})
45 return
46 }
47 c.JSON(http.StatusCreated, user)
48}
49 
50func messageFor(fe validator.FieldError) string {
51 switch fe.Tag() {
52 case "required":
53 return "this field is required"
54 case "email":
55 return "must be a valid email address"
56 case "min":
57 return "must be at least " + fe.Param() + " characters"
58 case "max":
59 return "must be at most " + fe.Param() + " characters"
60 case "alphanum":
61 return "must contain only letters and numbers"
62 case "gte":
63 return "must be " + fe.Param() + " or greater"
64 case "lte":
65 return "must be " + fe.Param() + " or less"
66 default:
67 return "is invalid"
68 }
69}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Struct binding tags let you declare validation rules right next to the fields they govern.
  2. 2Distinguishing malformed JSON from validation failures lets you return the right HTTP status for each.
  3. 3Translating raw validator tags into human messages keeps your API responses clean and client-friendly.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Turning Gin validation errors into JSON — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code