<h2>Introdução: Por Que Validar Dados em APIs?</h2>
<p>Quando você constrói uma API em Go, recebe dados de clientes externos — JSON, formulários, query parameters — e precisa garantir que esses dados atendem aos requisitos da sua aplicação antes de processá-los. Validação inadequada leva a erros em tempo de execução, comportamentos inesperados e vulnerabilidades de segurança. O <code>go-playground/validator</code> é a biblioteca Go mais popular para resolver esse problema de forma elegante e eficiente, permitindo que você defina regras de validação através de struct tags com uma sintaxe clara e expressiva.</p>
<p>Neste artigo, você aprenderá não apenas como usar a biblioteca, mas entenderá <strong>por quê</strong> cada abordagem funciona dessa forma e como aplicar isso em cenários reais de desenvolvimento de APIs.</p>
<h2>Conceitos Fundamentais do go-playground/validator</h2>
<h3>O que é Validação Declarativa?</h3>
<p>O <code>go-playground/validator</code> utiliza um padrão chamado <strong>validação declarativa</strong>, onde você descreve as regras de validação diretamente nas struct tags, sem escrever lógica condicional manual. Essa abordagem é superior a validações imperativas porque separa a lógica de negócio da lógica de validação, tornando o código mais legível e mantível.</p>
<p>Quando você marca um campo com tags como <code>validate:"required,email"</code>, você está dizendo: "este campo é obrigatório e deve ser um email válido". A biblioteca interpreta essas tags em tempo de validação e retorna erros estruturados caso as regras não sejam atendidas.</p>
<h3>Instalação e Setup Básico</h3>
<p>Para instalar a biblioteca:</p>
<pre><code class="language-bash">go get github.com/go-playground/validator/v10</code></pre>
<p>Uma validação básica em Go requer apenas:</p>
<pre><code class="language-go">package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
type User struct {
Name string validate:"required,min=3"
Email string validate:"required,email"
Age int validate:"required,min=18,max=120"
}
func main() {
validate := validator.New()
user := User{
Name: "Jo",
Email: "invalid-email",
Age: 15,
}
err := validate.Struct(user)
if err != nil {
for _, err := range err.(validator.ValidationErrors) {
fmt.Printf("Campo: %s, Tag: %s, Valor: %v\n",
err.Field(), err.Tag(), err.Value())
}
}
}</code></pre>
<p>Neste exemplo, a validação falhará em três pontos: <code>Name</code> tem menos de 3 caracteres, <code>Email</code> não é um formato válido de email, e <code>Age</code> é menor que 18. A biblioteca retorna um tipo <code>ValidationErrors</code> que você pode iterar para obter detalhes de cada falha.</p>
<h2>Integrando Validação em APIs Go</h2>
<h3>Validação em Handlers HTTP</h3>
<p>Em uma API RESTful real, você valida dados recebidos em requisições. A integração mais natural é fazer a validação logo após o unmarshal do JSON:</p>
<pre><code class="language-go">package main
import (
"encoding/json"
"fmt"
"net/http"
"github.com/go-playground/validator/v10"
)
type CreateUserRequest struct {
Name string json:"name" validate:"required,min=3,max=100"
Email string json:"email" validate:"required,email"
Password string json:"password" validate:"required,min=8"
Age int json:"age" validate:"required,min=18,max=120"
}
var validate = validator.New()
func createUserHandler(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
// Decodificar JSON
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "JSON inválido", http.StatusBadRequest)
return
}
// Validar estrutura
if err := validate.Struct(req); err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
// Construir resposta de erro detalhada
validationErrors := err.(validator.ValidationErrors)
errorResponse := make(map[string]string)
for _, err := range validationErrors {
errorResponse[err.Field()] = fmt.Sprintf(
"Falha na validação '%s'", err.Tag())
}
json.NewEncoder(w).Encode(map[string]interface{}{
"errors": errorResponse,
})
return
}
// Processar dados válidos
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{
"message": "Usuário criado com sucesso",
"name": req.Name,
})
}
func main() {
http.HandleFunc("/users", createUserHandler)
http.ListenAndServe(":8080", nil)
}</code></pre>
<p>Aqui, quando um POST chega em <code>/users</code>, o handler decodifica o JSON, valida a estrutura contra as tags, e retorna erros específicos para cada campo inválido. Isso é muito mais eficiente que validações manuais com dozens de <code>if</code> statements.</p>
<h3>Tags de Validação Mais Comuns</h3>
<p>As tags mais utilizadas no desenvolvimento real são:</p>
<ul>
<li><code>required</code>: Campo obrigatório</li>
<li><code>email</code>: Formato válido de email</li>
<li><code>min=X</code> e <code>max=X</code>: Tamanho mínimo e máximo de string ou slice</li>
<li><code>gt=X</code> e <code>lt=X</code>: Greater than e less than para números</li>
<li><code>gte=X</code> e <code>lte=X</code>: Greater than or equal, less than or equal</li>
<li><code>url</code>: Deve ser uma URL válida</li>
<li><code>uuid</code>: Deve ser um UUID válido</li>
<li><code>numeric</code>: Apenas caracteres numéricos</li>
<li><code>oneof=X Y Z</code>: Valor deve ser um de X, Y ou Z</li>
</ul>
<pre><code class="language-go">type Product struct {
ID string validate:"required,uuid"
Name string validate:"required,min=1,max=255"
Price float64 validate:"required,gt=0"
Status string validate:"required,oneof=active inactive archived"
Website string validate:"omitempty,url"
SKU string validate:"numeric"
}</code></pre>
<h2>Validações Avançadas e Customizadas</h2>
<h3>Validadores Customizados</h3>
<p>O poder real da biblioteca emerge quando você precisa de lógicas de validação específicas ao seu domínio. Você pode registrar validadores customizados antes de usar o validator:</p>
<pre><code class="language-go">package main
import (
"fmt"
"github.com/go-playground/validator/v10"
"regexp"
)
var validate = validator.New()
// Registrar validador customizado
func init() {
validate.RegisterValidation("cpf", validateCPF)
validate.RegisterValidation("strongpassword", validateStrongPassword)
}
// Validador para CPF (lógica simplificada)
func validateCPF(fl validator.FieldLevel) bool {
cpf := fl.Field().String()
// Verificar se tem 11 dígitos
if len(cpf) != 11 {
return false
}
// Verificar se não é sequência repetida
for i := 0; i < 10; i++ {
if cpf == string(rune(cpf[0])) {
return false
}
}
return true
}
// Validador para senha forte
func validateStrongPassword(fl validator.FieldLevel) bool {
password := fl.Field().String()
hasUpper := regexp.MustCompile("[A-Z]").MatchString(password)
hasLower := regexp.MustCompile("[a-z]").MatchString(password)
hasDigit := regexp.MustCompile("[0-9]").MatchString(password)
hasSpecial := regexp.MustCompile("[!@#$%^&*]").MatchString(password)
return hasUpper && hasLower && hasDigit && hasSpecial && len(password) >= 12
}
type BrazilianUser struct {
Name string validate:"required,min=3"
CPF string validate:"required,cpf"
Password string validate:"required,strongpassword"
}
func main() {
user := BrazilianUser{
Name: "João Silva",
CPF: "12345678900",
Password: "MyP@ssw0rd123",
}
if err := validate.Struct(user); err != nil {
fmt.Println("Validação falhou:", err)
} else {
fmt.Println("Usuário válido!")
}
}</code></pre>
<h3>Validação com Condições (Cross-Field)</h3>
<p>Às vezes, a validação de um campo depende do valor de outro campo. Use a tag <code>eqfield</code> ou registre validadores que acessem múltiplos campos:</p>
<pre><code class="language-go">type PasswordReset struct {
NewPassword string validate:"required,min=8"
ConfirmPassword string validate:"required,eqfield=NewPassword"
}
type DateRange struct {
StartDate string validate:"required,datetime=2006-01-02"
EndDate string validate:"required,datetime=2006-01-02"
}</code></pre>
<p>Na estrutura <code>PasswordReset</code>, o <code>ConfirmPassword</code> deve ser exatamente igual ao <code>NewPassword</code>. Isso é validação cross-field integrada. Para lógicas mais complexas, você cria validadores customizados que recebem a struct inteira:</p>
<pre><code class="language-go">validate.RegisterStructValidation(func(sl validator.StructLevel) {
obj := sl.Current().Interface().(DateRange)
if obj.EndDate <= obj.StartDate {
sl.ReportError(obj.EndDate, "EndDate", "enddate", "enddate", "")
}
}, DateRange{})</code></pre>
<h2>Boas Práticas e Padrões em Produção</h2>
<h3>Criando um Middleware de Validação</h3>
<p>Em aplicações maiores, você quer reutilizar a lógica de validação. Um middleware genérico reduz repetição:</p>
<pre><code class="language-go">package middleware
import (
"encoding/json"
"net/http"
"github.com/go-playground/validator/v10"
)
var validate = validator.New()
func ValidateJSON(expectedType interface{}) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Criar nova instância do tipo esperado
ptr := reflect.New(reflect.TypeOf(expectedType)).Interface()
if err := json.NewDecoder(r.Body).Decode(ptr); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{
"error": "JSON inválido",
})
return
}
if err := validate.Struct(ptr); err != nil {
w.WriteHeader(http.StatusUnprocessableEntity)
json.NewEncoder(w).Encode(map[string]interface{}{
"errors": formatValidationErrors(err),
})
return
}
// Passar dados validados via context
ctx := context.WithValue(r.Context(), "validated", ptr)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
func formatValidationErrors(err error) map[string]string {
result := make(map[string]string)
for _, err := range err.(validator.ValidationErrors) {
result[err.Field()] = err.Tag()
}
return result
}</code></pre>
<h3>Mensagens de Erro Amigáveis</h3>
<p>Erros técnicos (<code>tag validation failed</code>) não são úteis para clientes. Implemente um tradutor de mensagens:</p>
<pre><code class="language-go">package validation
import "github.com/go-playground/validator/v10"
var fieldMessages = map[string]map[string]string{
"Email": {
"required": "O email é obrigatório",
"email": "O email deve ser válido",
},
"Age": {
"required": "A idade é obrigatória",
"min": "A idade mínima é 18 anos",
"max": "A idade máxima é 120 anos",
},
}
func GetErrorMessage(err validator.FieldError) string {
field := err.Field()
tag := err.Tag()
if messages, exists := fieldMessages[field]; exists {
if msg, exists := messages[tag]; exists {
return msg
}
}
return "Campo inválido: " + field
}</code></pre>
<p>Agora seus erros são legíveis pelo cliente:</p>
<pre><code class="language-go">validationErrors := err.(validator.ValidationErrors)
for _, err := range validationErrors {
fmt.Println(GetErrorMessage(err))
// Saída: "O email deve ser válido"
}</code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu três pontos essenciais sobre validação em Go:</p>
<ol>
<li><strong>Validação declarativa via struct tags é mais eficiente que validação imperativa</strong>: A abordagem do <code>go-playground/validator</code> permite descrever regras uma única vez e reutilizá-las em toda a aplicação, reduzindo código duplicado e tornando mudanças triviais.</li>
</ol>
<ol>
<li><strong>Validadores customizados resolvem lógicas de domínio específicas</strong>: CPF, senhas fortes, datas futuras — você registra uma vez e usa em qualquer struct, mantendo código DRY e testável.</li>
</ol>
<ol>
<li><strong>Integração em middleware padroniza validação em APIs</strong>: Ao encapsular validação em middleware ou handlers reutilizáveis, você garante consistência, reduz bugs e deixa handlers focados em lógica de negócio, não em validação repetitiva.</li>
</ol>
<p>A chave para usar bem essa biblioteca é não apenas aplicar tags indiscriminadamente, mas pensar em quais regras definem a integridade dos seus dados e registrá-las uma vez de forma clara.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://github.com/go-playground/validator" target="_blank" rel="noopener noreferrer">go-playground/validator - Repositório Oficial</a></li>
<li><a href="https://pkg.go.dev/github.com/go-playground/validator/v10" target="_blank" rel="noopener noreferrer">Documentação do Validator v10</a></li>
<li><a href="https://github.com/golang/go/wiki/CodeReviewComments" target="_blank" rel="noopener noreferrer">Go Wiki: Code Review Comments - Validation</a></li>
<li><a href="https://golang.org/doc/effective_go#web_servers" target="_blank" rel="noopener noreferrer">Building Web APIs with Go - Capitulo sobre Validação</a></li>
<li><a href="https://restfulapi.net/input-validation/" target="_blank" rel="noopener noreferrer">REST API Best Practices - Input Validation</a></li>
</ul>
<p><!-- FIM --></p>