DEV Community

EstevĂŁo Lucas Lemes
EstevĂŁo Lucas Lemes

Posted on Edited on

Gin-Golang: Improving Validation Messages for Better API Responses 🍸

Customizing Validation Error Messages in Gin

If you've worked with validations in Gin, you may have encountered technical and difficult-to-understand error messages. In this article, I'll show you how to customize these messages to make them more user-friendly and clear.

The Problem with Default Messages in Gin

Imagine a user trying to create an account but entering an incorrect email. Instead of a clear message like "The Email field must be a valid email," they receive something like:

{
    "error": "Key: 'CreateUserInput.Email' Error:Field validation for 'Email' failed on the 'email' tag"
}
Enter fullscreen mode Exit fullscreen mode

This can be confusing, especially for end users or non-technical testers.

Creating a Middleware to Customize Error Messages

Gin allows us to add middleware to modify the request or response before they reach the client. In this case, our middleware captures validation errors and formats them in a more understandable way.

Let's consider the following basic structure for our project:

package main

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

type CreateUserInput struct {
    Name     string `json:"name" binding:"required"`
    Email    string `json:"email" binding:"required,email"`
    Password string `json:"password" binding:"required"`
}

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


    r.POST("/users", func(c *gin.Context) {
        var input CreateUserInput
        if err := c.ShouldBindJSON(&input); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }

        c.JSON(http.StatusOK, gin.H{"message": "User created successfully"})
    })

    r.Run(":8080")
}
Enter fullscreen mode Exit fullscreen mode

Now, let's create middleware to handle validation errors:

To create middleware, it's simple. We can use the following basic structure:

func BasicMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // Code to be executed before processing the request.
        fmt.Println("Before")

        // Call the next Handler in the middleware.
        c.Next()

        // Code to be executed after processing the request
        fmt.Println("After")
    }
}
Enter fullscreen mode Exit fullscreen mode

Following this basic structure, we'll create middleware that executes our logic after processing the request.

Before processing the error, we'll define a structure for our response, a mapping of the messages we want to change based on the binding tags, and a handler to process the error.

// Reponse struct to be returned as JSON
type ErrorMsg struct {
    Field   string `json:"field"`
    Message string `json:"message"`
}

// Map of error messages
var errorMessages = map[string]string{
    "required": "The field %s is required",
    "email":    "The field %s must be a valid email",
}

// function to get the error message based on the field error
func getErrorMsg(fe validator.FieldError) string {
    tag := fe.Tag()
    field := fe.StructNamespace()
    parts := strings.SplitN(field, ".", 2)
    field = parts[len(parts)-1]

    if msg, ok := errorMessages[tag]; ok {
        if fe.Param() != "" {
            return fmt.Sprintf(msg, field, fe.Param())
        }
        return fmt.Sprintf(msg, field)
    }

    return fmt.Sprintf("Validation error on field %s: %s", fe.Field(), tag)
}

func handleError(c *gin.Context, err error) {
    var ve validator.ValidationErrors
    if errors.As(err, &ve) {
        out := make([]ErrorMsg, len(ve))
        for i, fe := range ve {
            out[i] = ErrorMsg{fe.Field(), getErrorMsg(fe)}
        }
        c.JSON(http.StatusBadRequest, gin.H{"type": "ValidationError", "message": "Invalid Attributes", "errors": out})
        return
    }

}
Enter fullscreen mode Exit fullscreen mode

With the above code, we have the basic functions that will be used in our middleware, so we can now create it.

func ErrorHandlerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        if len(c.Errors) > 0 {
            err := c.Errors.Last().Err
            handleError(c, err)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that our middleware is created, we need to do two more steps:

  1. Add the middleware to the router for all routes.
  2. Pass the error to the context so that the error handler middleware can process and return the correct response.

The complete main.go file will look like this:

func main() {
    r := gin.Default()
    // Add the middleware to the route
    r.Use(ErrorHandlerMiddleware())

    r.POST("/users", func(c *gin.Context) {
        var input CreateUserInput
        if err := c.ShouldBindJSON(&input); err != nil {
            // The error is handled by the middleware
            if err := c.Error(err); err != nil {
                fmt.Print(err)
            }
            return
        }

        c.JSON(http.StatusOK, gin.H{"message": "User created successfully"})
    })

    r.Run(":8080")
}
Enter fullscreen mode Exit fullscreen mode

With this, we now have the customized error messages mapped to each validation tag.

{
    "errors": [
        {
            "field": "Email",
            "message": "The field Email must be a valid email"
        },
        {
            "field": "Password",
            "message": "The field Password is required"
        }
    ],
    "message": "Invalid Attributes",
    "type": "ValidationError"
}
Enter fullscreen mode Exit fullscreen mode

Postman response and request image

In this article, we saw how to customize error messages in Gin to make them more user-friendly and easier to understand. This technique can improve the experience for users and testers. If you enjoyed the article, leave a comment or share your experience with validations in Gin!

Top comments (2)

Collapse
 
carlosfiuza profile image
Carlos Eduardo Fiuza •

Nice!
Very interesting way to customize gin validation errors.
(In javascript is easier haha!)

Collapse
 
estevaolucas1 profile image
EstevĂŁo Lucas Lemes •

Thank you! Yes, I agree, JavaScript has its advantages in that aspect hahaha!