Go

Validação de Dados em APIs Go com go-playground/validator: Do Básico ao Avançado

14 min de leitura

Validação de Dados em APIs Go com go-playground/validator: Do Básico ao Avançado

Introdução: Por Que Validar Dados em APIs? 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 é 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. Neste artigo, você aprenderá não apenas como usar a biblioteca, mas entenderá por quê cada abordagem funciona dessa forma e como aplicar isso em cenários reais de desenvolvimento de APIs. Conceitos Fundamentais do go-playground/validator O que é Validação Declarativa? O utiliza um padrão chamado validação declarativa, 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

<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:&quot;required,email&quot;</code>, você está dizendo: &quot;este campo é obrigatório e deve ser um email válido&quot;. 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 (

&quot;fmt&quot;

&quot;github.com/go-playground/validator/v10&quot;

)

type User struct {

Name string validate:&quot;required,min=3&quot;

Email string validate:&quot;required,email&quot;

Age int validate:&quot;required,min=18,max=120&quot;

}

func main() {

validate := validator.New()

user := User{

Name: &quot;Jo&quot;,

Email: &quot;invalid-email&quot;,

Age: 15,

}

err := validate.Struct(user)

if err != nil {

for _, err := range err.(validator.ValidationErrors) {

fmt.Printf(&quot;Campo: %s, Tag: %s, Valor: %v\n&quot;,

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 (

&quot;encoding/json&quot;

&quot;fmt&quot;

&quot;net/http&quot;

&quot;github.com/go-playground/validator/v10&quot;

)

type CreateUserRequest struct {

Name string json:&quot;name&quot; validate:&quot;required,min=3,max=100&quot;

Email string json:&quot;email&quot; validate:&quot;required,email&quot;

Password string json:&quot;password&quot; validate:&quot;required,min=8&quot;

Age int json:&quot;age&quot; validate:&quot;required,min=18,max=120&quot;

}

var validate = validator.New()

func createUserHandler(w http.ResponseWriter, r *http.Request) {

var req CreateUserRequest

// Decodificar JSON

if err := json.NewDecoder(r.Body).Decode(&amp;req); err != nil {

http.Error(w, &quot;JSON inválido&quot;, http.StatusBadRequest)

return

}

// Validar estrutura

if err := validate.Struct(req); err != nil {

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

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(

&quot;Falha na validação &#039;%s&#039;&quot;, err.Tag())

}

json.NewEncoder(w).Encode(map[string]interface{}{

&quot;errors&quot;: errorResponse,

})

return

}

// Processar dados válidos

w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)

w.WriteHeader(http.StatusCreated)

json.NewEncoder(w).Encode(map[string]string{

&quot;message&quot;: &quot;Usuário criado com sucesso&quot;,

&quot;name&quot;: req.Name,

})

}

func main() {

http.HandleFunc(&quot;/users&quot;, createUserHandler)

http.ListenAndServe(&quot;:8080&quot;, 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:&quot;required,uuid&quot;

Name string validate:&quot;required,min=1,max=255&quot;

Price float64 validate:&quot;required,gt=0&quot;

Status string validate:&quot;required,oneof=active inactive archived&quot;

Website string validate:&quot;omitempty,url&quot;

SKU string validate:&quot;numeric&quot;

}</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 (

&quot;fmt&quot;

&quot;github.com/go-playground/validator/v10&quot;

&quot;regexp&quot;

)

var validate = validator.New()

// Registrar validador customizado

func init() {

validate.RegisterValidation(&quot;cpf&quot;, validateCPF)

validate.RegisterValidation(&quot;strongpassword&quot;, 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 &lt; 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(&quot;[A-Z]&quot;).MatchString(password)

hasLower := regexp.MustCompile(&quot;[a-z]&quot;).MatchString(password)

hasDigit := regexp.MustCompile(&quot;[0-9]&quot;).MatchString(password)

hasSpecial := regexp.MustCompile(&quot;[!@#$%^&amp;*]&quot;).MatchString(password)

return hasUpper &amp;&amp; hasLower &amp;&amp; hasDigit &amp;&amp; hasSpecial &amp;&amp; len(password) &gt;= 12

}

type BrazilianUser struct {

Name string validate:&quot;required,min=3&quot;

CPF string validate:&quot;required,cpf&quot;

Password string validate:&quot;required,strongpassword&quot;

}

func main() {

user := BrazilianUser{

Name: &quot;João Silva&quot;,

CPF: &quot;12345678900&quot;,

Password: &quot;MyP@ssw0rd123&quot;,

}

if err := validate.Struct(user); err != nil {

fmt.Println(&quot;Validação falhou:&quot;, err)

} else {

fmt.Println(&quot;Usuário válido!&quot;)

}

}</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:&quot;required,min=8&quot;

ConfirmPassword string validate:&quot;required,eqfield=NewPassword&quot;

}

type DateRange struct {

StartDate string validate:&quot;required,datetime=2006-01-02&quot;

EndDate string validate:&quot;required,datetime=2006-01-02&quot;

}</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 &lt;= obj.StartDate {

sl.ReportError(obj.EndDate, &quot;EndDate&quot;, &quot;enddate&quot;, &quot;enddate&quot;, &quot;&quot;)

}

}, 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 (

&quot;encoding/json&quot;

&quot;net/http&quot;

&quot;github.com/go-playground/validator/v10&quot;

)

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{

&quot;error&quot;: &quot;JSON inválido&quot;,

})

return

}

if err := validate.Struct(ptr); err != nil {

w.WriteHeader(http.StatusUnprocessableEntity)

json.NewEncoder(w).Encode(map[string]interface{}{

&quot;errors&quot;: formatValidationErrors(err),

})

return

}

// Passar dados validados via context

ctx := context.WithValue(r.Context(), &quot;validated&quot;, 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 &quot;github.com/go-playground/validator/v10&quot;

var fieldMessages = map[string]map[string]string{

&quot;Email&quot;: {

&quot;required&quot;: &quot;O email é obrigatório&quot;,

&quot;email&quot;: &quot;O email deve ser válido&quot;,

},

&quot;Age&quot;: {

&quot;required&quot;: &quot;A idade é obrigatória&quot;,

&quot;min&quot;: &quot;A idade mínima é 18 anos&quot;,

&quot;max&quot;: &quot;A idade máxima é 120 anos&quot;,

},

}

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 &quot;Campo inválido: &quot; + 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: &quot;O email deve ser válido&quot;

}</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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Go

Como Usar Construindo APIs REST em Go sem Framework: Roteamento e Middlewares em Produção
Como Usar Construindo APIs REST em Go sem Framework: Roteamento e Middlewares em Produção

Fundamentos de APIs REST em Go Puro Go é uma linguagem extremamente eficiente...

O que Todo Dev Deve Saber sobre Build e Cross-Compilation em Go: Binários para Múltiplas Plataformas
O que Todo Dev Deve Saber sobre Build e Cross-Compilation em Go: Binários para Múltiplas Plataformas

Build e Cross-Compilation em Go: Dominando Binários para Múltiplas Plataforma...

Select em Go: Multiplexando Channels e Timeouts: Do Básico ao Avançado
Select em Go: Multiplexando Channels e Timeouts: Do Básico ao Avançado

Introdução ao Select em Go O é uma das construções mais poderosas da linguage...