Validate User Input in a Go Web App with Gin
Input validation is one of the first defenses a web application has against malformed, incomplete, or unexpected data. Validation should run before business logic writes data to a database or triggers other side effects.
This example uses Gin with go-playground/validator to validate a simple registration request.
Project Structure
project/
├── main.go
└── utils/
└── validation.go1. Set Up Gin
Create main.go:
package main
import (
"net/http"
"website/utils/validation"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
)
type User struct {
Name string `json:"name" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
var validate = validator.New()
func main() {
r := gin.Default()
r.POST("/register", registerHandler)
if err := r.Run(":8080"); err != nil {
panic(err)
}
}
func registerHandler(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
return
}
validationErrors := validation.ValidateStruct(validate, user)
if len(validationErrors) > 0 {
c.JSON(http.StatusUnprocessableEntity, gin.H{"errors": validationErrors})
return
}
c.JSON(http.StatusOK, gin.H{"message": "Registration successful"})
}The validate instance is created once and reused instead of being allocated for every request.
2. Create a Validation Utility
Create utils/validation.go:
package validation
import (
"fmt"
"strings"
"github.com/go-playground/validator/v10"
)
type ValidationError struct {
Field string `json:"field"`
Message string `json:"message"`
}
func ValidateStruct(validate *validator.Validate, data any) []ValidationError {
var errors []ValidationError
if err := validate.Struct(data); err != nil {
validationErrors, ok := err.(validator.ValidationErrors)
if !ok {
return []ValidationError{{Message: "validation failed"}}
}
for _, fieldError := range validationErrors {
field := strings.ToLower(fieldError.Field())
var message string
switch fieldError.Tag() {
case "required":
message = fmt.Sprintf("%s is required", field)
case "min":
message = fmt.Sprintf("%s must contain at least %s characters", field, fieldError.Param())
case "email":
message = "email format is invalid"
default:
message = fmt.Sprintf("%s is invalid (%s)", field, fieldError.Tag())
}
errors = append(errors, ValidationError{Field: field, Message: message})
}
}
return errors
}3. Run and Test the API
go run main.goSend an invalid request:
curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"name":"Al","email":"not-an-email","password":"123"}'Gin will return a structured validation response instead of allowing invalid data to continue through the registration workflow.
Validation Is Not Sanitization
Validation answers whether input satisfies your application’s rules. It does not replace output escaping, SQL parameterization, authorization, or other security controls. For example, GORM and database drivers should still use bound parameters rather than constructing SQL from raw user input.
Conclusion
Gin and go-playground/validator provide a compact way to enforce request rules and return useful errors to clients. Keep validation rules close to request types, reuse your validator, and treat validation as one layer of a broader input-handling and security strategy.