Middleware y patrones avanzados con net/http

Avanzado
Go
Go
Actualizado: 19/04/2026

¿Qué es middleware en Go?

Un middleware es una función que envuelve un http.Handler para añadir funcionalidad antes y/o después de que el handler principal procese la petición. La interfaz clave es:

Middleware HTTP en Go: cadena de middlewares y patrón de composición

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

// Patrón de middleware: función que recibe y devuelve http.Handler
type Middleware func(http.Handler) http.Handler

Enrutamiento mejorado en Go 1.22

Go 1.22 mejoró significativamente http.ServeMux para soportar métodos HTTP y comodines en los patrones:

mux := http.NewServeMux()

// Método + ruta exacta
mux.HandleFunc("GET /api/productos", listarProductos)
mux.HandleFunc("POST /api/productos", crearProducto)

// Parámetro de ruta con {nombre}
mux.HandleFunc("GET /api/productos/{id}", obtenerProducto)
mux.HandleFunc("PUT /api/productos/{id}", actualizarProducto)
mux.HandleFunc("DELETE /api/productos/{id}", eliminarProducto)

// Extraer el parámetro en el handler
func obtenerProducto(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id") // disponible desde Go 1.22
    // ...
}

Middleware de logging

func middlewareLogging(logger *slog.Logger) func(http.Handler) http.Handler {
    return func(siguiente http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            inicio := time.Now()

            // Capturar el status code con un ResponseWriter personalizado
            rw := &responseWriter{ResponseWriter: w, status: http.StatusOK}

            siguiente.ServeHTTP(rw, r)

            logger.Info("petición HTTP",
                slog.String("método", r.Method),
                slog.String("ruta", r.URL.Path),
                slog.Int("status", rw.status),
                slog.Duration("duración", time.Since(inicio)),
                slog.String("ip", r.RemoteAddr),
            )
        })
    }
}

// ResponseWriter que captura el status code
type responseWriter struct {
    http.ResponseWriter
    status int
}

func (rw *responseWriter) WriteHeader(status int) {
    rw.status = status
    rw.ResponseWriter.WriteHeader(status)
}

Middleware de recuperación de pánicos

func middlewareRecuperacion(logger *slog.Logger) func(http.Handler) http.Handler {
    return func(siguiente http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            defer func() {
                if err := recover(); err != nil {
                    logger.Error("pánico recuperado",
                        slog.Any("error", err),
                        slog.String("ruta", r.URL.Path),
                    )
                    http.Error(w, "Error interno del servidor",
                        http.StatusInternalServerError)
                }
            }()
            siguiente.ServeHTTP(w, r)
        })
    }
}

Middleware de CORS

func middlewareCORS(originesPermitidos []string) func(http.Handler) http.Handler {
    origenSet := make(map[string]bool)
    for _, o := range originesPermitidos {
        origenSet[o] = true
    }

    return func(siguiente http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            origen := r.Header.Get("Origin")

            if origenSet[origen] || origenSet["*"] {
                w.Header().Set("Access-Control-Allow-Origin", origen)
                w.Header().Set("Access-Control-Allow-Methods",
                    "GET, POST, PUT, DELETE, OPTIONS")
                w.Header().Set("Access-Control-Allow-Headers",
                    "Content-Type, Authorization")
            }

            // Responder a peticiones preflight OPTIONS
            if r.Method == http.MethodOptions {
                w.WriteHeader(http.StatusNoContent)
                return
            }

            siguiente.ServeHTTP(w, r)
        })
    }
}

Encadenar middlewares

// Función de composición que aplica middlewares de derecha a izquierda
func encadenar(h http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
    for i := len(middlewares) - 1; i >= 0; i-- {
        h = middlewares[i](h)
    }
    return h
}

// Uso
func main() {
    logger := slog.Default()
    mux := http.NewServeMux()

    // Registrar handlers...
    mux.HandleFunc("GET /api/productos", listarProductos)

    // Aplicar middleware a todo el servidor
    servidor := encadenar(mux,
        middlewareRecuperacion(logger),
        middlewareLogging(logger),
        middlewareCORS([]string{"https://miapp.com"}),
    )

    http.ListenAndServe(":8080", servidor)
}

Helpers para respuestas JSON

func responderJSON(w http.ResponseWriter, status int, datos any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    if err := json.NewEncoder(w).Encode(datos); err != nil {
        slog.Error("error al codificar respuesta JSON", "err", err)
    }
}

func responderError(w http.ResponseWriter, status int, mensaje string) {
    responderJSON(w, status, map[string]string{"error": mensaje})
}

// Uso en handlers
func listarProductos(w http.ResponseWriter, r *http.Request) {
    productos, err := repo.ObtenerTodos(r.Context())
    if err != nil {
        responderError(w, http.StatusInternalServerError, "error al obtener productos")
        return
    }
    responderJSON(w, http.StatusOK, productos)
}

func crearProducto(w http.ResponseWriter, r *http.Request) {
    var p Producto
    if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
        responderError(w, http.StatusBadRequest, "JSON inválido")
        return
    }

    id, err := repo.Crear(r.Context(), p)
    if err != nil {
        responderError(w, http.StatusInternalServerError, "error al crear producto")
        return
    }

    p.ID = id
    responderJSON(w, http.StatusCreated, p)
}

Servidor HTTP completo con contexto y apagado limpio

func NuevoServidor(repo ProductoRepositorio, logger *slog.Logger) *http.Server {
    mux := http.NewServeMux()

    mux.HandleFunc("GET /api/productos", listarProductos(repo))
    mux.HandleFunc("POST /api/productos", crearProducto(repo))
    mux.HandleFunc("GET /api/productos/{id}", obtenerProducto(repo))

    handler := encadenar(mux,
        middlewareRecuperacion(logger),
        middlewareLogging(logger),
    )

    return &http.Server{
        Addr:         ":8080",
        Handler:      handler,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 30 * time.Second,
        IdleTimeout:  120 * time.Second,
    }
}
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Go es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Go

Explora más contenido relacionado con Go y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Implementar middleware como http.Handler decoradores en Go. Encadenar múltiples middlewares con funciones de composición. Crear middleware de logging, recuperación de pánico y autenticación básica. Usar los patrones de enrutamiento mejorados de Go 1.22 (métodos y wildcards en patrones). Gestionar cabeceras CORS con middleware. Servir respuestas JSON con helpers reutilizables.

Cursos que incluyen esta lección

Esta lección forma parte de los siguientes cursos estructurados con rutas de aprendizaje