Concepto clave
En el desarrollo de APIs REST, un CRUD completo (Create, Read, Update, Delete) es la base de cualquier microservicio que maneje datos. Piensa en una biblioteca digital: necesitas agregar nuevos libros (Create), consultar el catálogo (Read), actualizar información de ejemplares (Update) y retirar libros obsoletos (Delete). La validación actúa como el bibliotecario que verifica que cada libro tenga título, autor y ISBN válidos antes de ingresarlo al sistema.
En Go con gorilla/mux, implementar CRUD con validación robusta significa combinar el enrutamiento eficiente del paquete con estructuras de datos bien definidas y validación en tiempo real. Esto evita que datos corruptos o malformados lleguen a tu base de datos, similar a cómo un filtro de correo bloquea spam antes de que inunde tu bandeja de entrada.
Cómo funciona en la práctica
Vamos a construir un microservicio para gestionar productos en un e-commerce. Primero, definimos una estructura Product con campos como ID, nombre, precio y stock. Luego, creamos rutas HTTP para cada operación CRUD usando gorilla/mux:
- POST /products: Crea un nuevo producto con validación de campos obligatorios y rangos de precio.
- GET /products/{id}: Recupera un producto específico por su ID.
- PUT /products/{id}: Actualiza un producto existente, validando que los nuevos datos sean coherentes.
- DELETE /products/{id}: Elimina un producto, verificando que exista antes de proceder.
La validación se implementa usando el paquete go-playground/validator, que permite definir reglas como "required", "gt=0" para precios positivos, o "max=100" para límites de stock. Cada handler verifica estas reglas antes de procesar la solicitud, devolviendo errores HTTP 400 (Bad Request) si fallan.
Código en acción
Aquí tienes la estructura base y un ejemplo de handler con validación:
package main
import (
"encoding/json"
"net/http"
"github.com/go-playground/validator/v10"
"github.com/gorilla/mux"
)
type Product struct {
ID string `json:"id" validate:"omitempty,uuid"`
Name string `json:"name" validate:"required,min=3,max=100"`
Price float64 `json:"price" validate:"required,gt=0"`
Stock int `json:"stock" validate:"gte=0"`
}
var validate = validator.New()
func createProductHandler(w http.ResponseWriter, r *http.Request) {
var product Product
if err := json.NewDecoder(r.Body).Decode(&product); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
if err := validate.Struct(product); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Lógica para guardar en base de datos (ejemplo simplificado)
product.ID = generateUUID()
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(product)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/products", createProductHandler).Methods("POST")
// Agregar más handlers para GET, PUT, DELETE
http.ListenAndServe(":8080", r)
}Ahora, mejoremos el handler para incluir manejo de errores más detallado:
func createProductHandler(w http.ResponseWriter, r *http.Request) {
var product Product
if err := json.NewDecoder(r.Body).Decode(&product); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
if err := validate.Struct(product); err != nil {
respondWithError(w, http.StatusBadRequest, formatValidationError(err))
return
}
// Simular guardado en DB
product.ID = uuid.New().String()
if err := saveProduct(product); err != nil {
respondWithError(w, http.StatusInternalServerError, "Could not save product")
return
}
respondWithJSON(w, http.StatusCreated, product)
}
func respondWithError(w http.ResponseWriter, code int, message string) {
respondWithJSON(w, code, map[string]string{"error": message})
}
func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(payload)
}Errores comunes
- Validación solo en el cliente: Confiar únicamente en validación frontend deja tu API vulnerable a ataques. Siempre valida en el servidor, como verificar firmas en documentos legales.
- Falta de manejo de errores específicos: Devolver "error" genérico dificulta debugging. Usa códigos HTTP precisos (400, 404, 500) y mensajes claros.
- Ignorar concurrencia en operaciones CRUD: En microservicios de alto rendimiento, múltiples requests pueden modificar el mismo dato. Usa transacciones o locks para evitar condiciones de carrera.
- No limitar tamaños de request: Aceptar JSON muy grandes puede saturar memoria. Configura límites con
http.MaxBytesReader. - Olvidar sanitización de datos: La validación no previene inyecciones SQL o XSS. Usa parámetros preparados en queries y escapa outputs HTML.
Checklist de dominio
- Definí estructuras Go con tags de validación para todos los campos requeridos.
- Implementé handlers para las cuatro operaciones CRUD (POST, GET, PUT, DELETE) usando gorilla/mux.
- Integré el validador go-playground/validator en cada endpoint que recibe datos.
- Probé la API con herramientas como curl o Postman, enviando datos válidos e inválidos.
- Agregué manejo de errores específicos con respuestas JSON estandarizadas.
- Configuré límites de tamaño de request para prevenir ataques de denegación de servicio.
- Documenté los endpoints con ejemplos de request/response para futuros desarrolladores.
Implementa un CRUD para gestión de usuarios con validación avanzada
Construye un microservicio en Go que gestione usuarios para una plataforma online. Sigue estos pasos:
- Crea una estructura User con campos: ID (string), Email (string), Password (string), Age (int), y Active (bool). Aplica validaciones: Email debe ser válido y único, Password mínimo 8 caracteres, Age entre 18 y 100, Active opcional (default true).
- Configura gorilla/mux con estos endpoints:
- POST /users - Crea usuario, validando todos los campos.
- GET /users/{id} - Retorna usuario por ID, manejando no encontrado (404).
- PUT /users/{id} - Actualiza usuario, permitiendo actualizar solo Email, Age y Active (Password requiere endpoint separado).
- DELETE /users/{id} - Elimina usuario cambiando Active a false (borrado lógico).
- Implementa una validación custom para verificar que el Email no esté duplicado (simula con un mapa en memoria).
- Agrega middleware para loguear cada request con método, ruta y status code.
- Prueba con al menos 5 requests diferentes, incluyendo casos de error.
- Usa validator.RegisterValidation para crear una validación custom de email único.
- Para el borrado lógico, en el handler DELETE actualiza el campo Active en lugar de remover el dato.
- Considera usar sync.RWMutex si manejas concurrencia en el mapa de usuarios.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.