Concepto clave
El manejo de errores en APIs REST no es solo capturar excepciones, sino comunicar claramente al cliente qué salió mal y cómo proceder. Imagina un restaurante donde el camarero no solo dice "no hay", sino explica "el plato está agotado, pero tenemos estas alternativas similares". En Go, esto se traduce en respuestas HTTP personalizadas que incluyen códigos de estado apropiados (como 400, 404, 500), mensajes claros y detalles útiles en el cuerpo de la respuesta.
Un error bien manejado transforma una frustración en una experiencia controlada. Para APIs de alto rendimiento, es crucial que este proceso sea eficiente y no degrade el rendimiento. Usaremos el paquete gorilla/mux para enrutamiento y estructuras personalizadas para respuestas consistentes.
Cómo funciona en la práctica
Primero, definimos una estructura común para todas las respuestas de error. Esto asegura consistencia en la API. Luego, creamos funciones helper que generen respuestas HTTP con el código de estado y mensaje adecuados. Finalmente, integramos estos helpers en nuestros manejadores para capturar errores y devolver respuestas personalizadas.
Paso a paso: 1) Define un struct para respuestas de error. 2) Crea funciones que construyan respuestas HTTP. 3) Usa recover en middlewares para capturar pánicos. 4) Valida entradas y devuelve errores temprano. 5) Documenta los códigos de error en la API.
Código en acción
Aquí un ejemplo básico de estructura de error y función helper:
package main
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
)
// ErrorResponse define la estructura estándar para errores
type ErrorResponse struct {
Status int `json:"status"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
// sendError envía una respuesta de error personalizada
func sendError(w http.ResponseWriter, status int, message, details string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(ErrorResponse{
Status: status,
Message: message,
Details: details,
})
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/api/data/{id}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
if id == "" {
sendError(w, http.StatusBadRequest, "ID inválido", "El parámetro ID no puede estar vacío")
return
}
// Lógica de negocio aquí
w.WriteHeader(http.StatusOK)
w.Write([]byte("{\"data\": \"éxito\"}"))
})
http.ListenAndServe(":8080", r)
}Refactorización para incluir un middleware de recuperación de pánicos:
func panicRecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic recuperado: %v", err)
sendError(w, http.StatusInternalServerError, "Error interno del servidor", "")
}
}()
next.ServeHTTP(w, r)
})
}
// Uso en main
func main() {
r := mux.NewRouter()
r.Use(panicRecoveryMiddleware)
// ... rutas
}Errores comunes
- Devolver errores genéricos 500 para todo: Usa códigos específicos como 400 para errores del cliente, 404 para recursos no encontrados, y 500 solo para fallos internos inesperados.
- Exponer detalles internos en producción: En entornos de producción, omite o generaliza el campo "details" para evitar fugas de información sensible.
- No validar entradas temprano: Valida parámetros y cuerpos de solicitud al inicio del manejador para devolver errores rápidos y ahorrar recursos.
- Olvidar setear el Content-Type: Siempre establece w.Header().Set("Content-Type", "application/json") antes de escribir la respuesta.
- Ignorar pánicos: Usa middlewares con recover para capturar pánicos y evitar que la aplicación se detenga abruptamente.
Checklist de dominio
- ¿Defines una estructura estándar para respuestas de error en tu API?
- ¿Usas códigos de estado HTTP apropiados (400, 401, 404, 500) según el tipo de error?
- ¿Implementas un middleware para recuperación de pánicos en gorilla/mux?
- ¿Validas entradas al inicio de los manejadores y devuelves errores tempranos?
- ¿Documentas los posibles errores y sus códigos en la documentación de la API?
- ¿Pruebas escenarios de error con herramientas como curl o Postman?
- ¿Mantienes consistencia en el formato de respuesta entre endpoints?
Implementa un sistema de manejo de errores para un microservicio de usuarios
En este ejercicio, crearás un manejador de errores completo para un endpoint de API que gestiona usuarios. Sigue estos pasos:
- Crea un nuevo proyecto Go e instala gorilla/mux:
go get -u github.com/gorilla/mux. - Define un struct
Usercon campos ID, Name, y Email, y un structErrorResponsecomo en la lección. - Implementa una función
sendErrorque envíe respuestas JSON de error. - Crea un endpoint POST
/api/usersque valide: Name no vacío, Email con formato válido (usa regex simple). Si la validación falla, devuelve error 400 con mensaje claro. - Añade un middleware de recuperación de pánicos que capture cualquier panic y devuelva error 500.
- Simula un error interno lanzando un panic si el Email contiene "test" (solo para prueba).
- Prueba con curl: envía solicitudes inválidas y verifica las respuestas de error.
- Usa regexp.MustCompile para validar el email de forma simple, como `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`.
- En el middleware, recuerda llamar a next.ServeHTTP(w, r) dentro del defer para asegurar la ejecución normal.
- Para probar, usa curl -X POST http://localhost:8080/api/users -H "Content-Type: application/json" -d '{"name":"","email":"invalid"}'.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.