Como Criar uma API REST com Golang e Gin - Parte 3: Validação e Middleware

Na Parte 2 criamos o CRUD completo. Agora vamos adicionar validação, middleware e tratamento de erros.

Validação com Struct Tags

O Gin usa a biblioteca validator para validar dados. Adicione tags nas structs:

type Book struct {
    ID     string  `json:"id" binding:"required"`
    Title  string  `json:"title" binding:"required,min=3,max=100"`
    Author string  `json:"author" binding:"required"`
    Price  float64 `json:"price" binding:"required,gt=0"`
}

Agora o ShouldBindJSON valida automaticamente:

func createBook(c *gin.Context) {
    var newBook Book
    if err := c.ShouldBindJSON(&newBook); err != nil {
        c.JSON(400, gin.H{
            "error":   "Dados inválidos",
            "details": err.Error(),
        })
        return
    }
    books = append(books, newBook)
    c.JSON(201, newBook)
}

Criando Middleware

Middleware de Logging

func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        
        // Processa a requisição
        c.Next()
        
        // Após a requisição
        latency := time.Since(start)
        gin.DefaultWriter.Write([]byte(fmt.Sprintf(
            "[%s] %s %s %v\n",
            time.Now().Format("2006-01-02 15:04:05"),
            c.Request.Method,
            c.Request.URL.Path,
            latency,
        )))
    }
}

Middleware de Autenticação (API Key)

func AuthMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        apiKey := c.GetHeader("X-API-Key")
        if apiKey != "meu-secret-key" {
            c.AbortWithStatusJSON(401, gin.H{
                "error": "Não autorizado",
            })
            return
        }
        c.Next()
    }
}

Aplicando os Middleware

func main() {
    r := gin.Default()
    
    // Middleware global
    r.Use(LoggerMiddleware())
    
    // Grupo de rotas protegidas
    books := r.Group("/books")
    books.Use(AuthMiddleware())
    {
        books.GET("", getBooks)
        books.GET("/:id", getBook)
        books.POST("", createBook)
        books.PUT("/:id", updateBook)
        books.DELETE("/:id", deleteBook)
    }
    
    r.Run(":8080")
}

Tratamento de Erros Estruturado

Crie um helper para erros:

type AppError struct {
    Message string `json:"message"`
    Code    int    `json:"code"`
}

func NewAppError(message string, code int) *AppError {
    return &AppError{Message: message, Code: code}
}

func (e *AppError) Error() string {
    return e.Message
}

Uso nos handlers:

func getBook(c *gin.Context) {
    id := c.Param("id")
    for _, book := range books {
        if book.ID == id {
            c.JSON(200, book)
            return
        }
    }
    c.JSON(404, AppError{Message: "Livro não encontrado", Code: 404})
}

Testando

# Listar livros (precisa do header X-API-Key)
curl -H "X-API-Key: meu-secret-key" http://localhost:8080/books

# Criar livro com dados inválidos
curl -X POST http://localhost:8080/books \
  -H "X-API-Key: meu-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"title":"AB","price":-10}'

# Resposta de erro:
# {"error":"Dados inválidos","details":"Key: 'Book.Title' Error:Field validation for 'Title' failed on the 'min' tag..."}

Próximos Passos

Na Parte 4 (final), vamos:

  • Conectar ao PostgreSQL com GORM
  • Implementar migrations
  • Deploy com Docker

👉 Parte 4: Banco de Dados com GORM


Gostou deste tutorial? Compartilhe no Twitter e deixe seu feedback no Telegram!