Apps Artificial Intelligence CSS DevOps Go JavaScript Laravel Linux MongoDB MySQL PHP Python Rust Svelte Vue

Validate User Input in a Go Web App with Gin

2 min read .
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.go

1. 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.go

Send 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.

Related Posts

chevron-up