Uso de parámetros y consultas en rutas con gorilla/mux
En el desarrollo de APIs REST, la capacidad de definir rutas dinámicas y procesar parámetros de consulta es fundamental. Gorilla/mux, un poderoso enrutador HTTP para Go, supera las limitaciones del enrutador estándar de la biblioteca net/http, ofreciendo un control granular y expresivo sobre cómo se definen y capturan las variables en las URLs. Esta lección se adentra en las técnicas avanzadas para manejar parámetros de ruta y consultas, permitiéndote construir endpoints flexibles, seguros y listos para producción. Dominar estos conceptos es el puente entre una API rígida y una que puede adaptarse a las complejas necesidades de los clientes y las aplicaciones modernas.
Concepto Clave: Parámetros de Ruta vs. Parámetros de Consulta
Antes de sumergirnos en el código, es crucial distinguir entre dos tipos fundamentales de datos que llegan a través de la URL: los parámetros de ruta y los parámetros de consulta. Los parámetros de ruta son segmentos variables incrustados en la propia estructura de la URL. Por ejemplo, en la ruta /usuarios/{id}/perfil, {id} es un parámetro de ruta que identifica de manera única un recurso específico (el usuario con ID 123). Forman parte de la jerarquía del recurso y son esenciales para operaciones CRUD (Crear, Leer, Actualizar, Eliminar) sobre entidades específicas.
Por otro lado, los parámetros de consulta son pares clave-valor opcionales que se añaden al final de la URL después de un signo de interrogación (?), como en /productos?categoria=electronica&orden=precio. Aquí, categoria y orden son parámetros de consulta. No forman parte de la ruta base, sino que se utilizan para filtrar, ordenar, paginar o modificar la consulta sobre una colección de recursos. Mientras los parámetros de ruta suelen ser obligatorios para identificar un recurso, los de consulta son casi siempre opcionales y proporcionan contexto adicional a la solicitud.
Tip: Una analogía útil es pensar en una biblioteca. Los parámetros de ruta son como el número de estantería y el número específico de libro (ej: "Estantería 5, Libro 12"), que te llevan directamente a un ejemplar concreto. Los parámetros de consulta son como los criterios que introduces en el catálogo: "Género: Ciencia Ficción, Ordenado por: Año de publicación", que te devuelven una lista filtrada y ordenada de libros.
Cómo funciona en la práctica: Definiendo y Capturando Variables
Gorilla/mux introduce una sintaxis intuitiva y potente para definir rutas con parámetros. Utilizando la función mux.NewRouter() se crea un nuevo enrutador. Luego, para definir una ruta con un parámetro, se utiliza la notación de llaves {} dentro del patrón de la ruta, por ejemplo: /api/articulos/{id}. El nombre dentro de las llaves (en este caso, "id") es la clave con la que podrás acceder al valor capturado desde el código del manejador (handler).
Para capturar el valor de un parámetro de ruta dentro de tu función manejadora, gorilla/mux proporciona el método mux.Vars(r). Este método recibe el objeto http.Request y devuelve un mapa map[string]string donde las claves son los nombres de los parámetros definidos en la ruta y los valores son las cadenas extraídas de la URL solicitada. Es tu responsabilidad luego convertir esa cadena al tipo de dato adecuado (entero, UUID, etc.) y validarla. Para los parámetros de consulta, se sigue utilizando el método estándar r.URL.Query() de Go, que devuelve un objeto url.Values, facilitando el acceso a múltiples valores para una misma clave.
Un paso avanzado es la aplicación de restricciones o matchers a los parámetros. Gorilla/mux permite validar el formato de un parámetro directamente en la definición de la ruta usando expresiones regulares o métodos predefinidos. Por ejemplo, puedes restringir un parámetro {id} para que solo coincida con secuencias numéricas usando un patrón como {id:[0-9]+}. Esto no solo mejora la precisión del enrutamiento, evitando que rutas malformadas lleguen a tu lógica de negocio, sino que también sirve como una primera capa de validación muy eficiente.
Código en Acción: Un Ejemplo Completo y Funcional
A continuación, presentamos un ejemplo integral que demuestra la definición de rutas con parámetros, la captura de ambos tipos de variables, la aplicación de restricciones y el manejo básico de errores. Este código representa un módulo de una API para gestionar una biblioteca de artículos.
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
// Estructura de datos para un Artículo
type Articulo struct {
ID int `json:"id"`
Titulo string `json:"titulo"`
Autor string `json:"autor"`
Publicado bool `json:"publicado"`
}
// Base de datos en memoria (simulada)
var articulos = map[int]Articulo{
1: {ID: 1, Titulo: "Aprendiendo Go", Autor: "Carlos Ruiz", Publicado: true},
2: {ID: 2, Titulo: "APIs con Gorilla Mux", Autor: "Ana López", Publicado: true},
3: {ID: 3, Titulo: "Patrones de Microservicios", Autor: "Pedro Martínez", Publicado: false},
}
// Handler para obtener un artículo por su ID (Parámetro de Ruta)
func obtenerArticuloPorID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
idStr, ok := vars["id"]
if !ok {
http.Error(w, "ID no proporcionado en la ruta", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "ID debe ser un número entero válido", http.StatusBadRequest)
return
}
articulo, existe := articulos[id]
if !existe {
http.Error(w, "Artículo no encontrado", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(articulo)
}
// Handler para listar artículos con filtros (Parámetros de Consulta)
func listarArticulos(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
autorFiltro := query.Get("autor")
soloPublicados := query.Get("publicado") == "true"
resultados := []Articulo{}
for _, articulo := range articulos {
// Filtrar por autor si se proporciona
if autorFiltro != "" && articulo.Autor != autorFiltro {
continue
}
// Filtrar por estado de publicación si se solicita
if soloPublicados && !articulo.Publicado {
continue
}
resultados = append(resultados, articulo)
}
if len(resultados) == 0 {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "No se encontraron artículos con los criterios especificados")
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resultados)
}
func main() {
r := mux.NewRouter()
// Ruta con parámetro de ruta RESTRINGIDO a números
// Coincide con: /api/articulos/1
// No coincide con: /api/articulos/abc
r.HandleFunc("/api/articulos/{id:[0-9]+}", obtenerArticuloPorID).Methods("GET")
// Ruta para listar, acepta parámetros de consulta 'autor' y 'publicado'
// Ejemplos:
// /api/articulos
// /api/articulos?autor=Ana%20López
// /api/articulos?publicado=true
// /api/articulos?autor=Pedro%20Martínez&publicado=false
r.HandleFunc("/api/articulos", listarArticulos).Methods("GET")
// Ruta de ejemplo con MÚLTIPLES parámetros de ruta
r.HandleFunc("/categoria/{cat}/articulo/{id}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
fmt.Fprintf(w, "Categoría: %s, ID Artículo: %s", vars["cat"], vars["id"])
}).Methods("GET")
log.Println("Servidor iniciado en http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", r))
}
Este ejemplo inicia un servidor HTTP en el puerto 8080. Puedes probar los siguientes endpoints: GET /api/articulos/1 devolverá el artículo con ID 1. GET /api/articulos?publicado=true devolverá solo los artículos publicados. GET /api/articulos?autor=Ana%20López filtrará por autor. Observa cómo la ruta con el parámetro {id:[0-9]+} rechazará automáticamente solicitudes con IDs no numéricos, delegando el manejo de errores de formato antes de llegar al handler.
Errores Comunes y Cómo Evitarlos
Al trabajar con parámetros en gorilla/mux, es fácil caer en ciertos errores que pueden comprometer la robustez, seguridad y desempeño de tu API.
1. No Validar ni Sanitizar los Parámetros Capturados: Asumir que el valor de mux.Vars(r) o r.URL.Query().Get() es seguro y está en el formato correcto es un error grave. Siempre valida el tipo (conversión de string a int, etc.), la longitud, y el rango de los valores. Para consultas, valida contra inyecciones SQL o lógicas de negocio. Usa funciones como strconv.Atoi con manejo de errores y considera librerías de validación para estructuras complejas.
2. Confundir Parámetros de Ruta con Parámetros de Consulta: Utilizar un parámetro de consulta para identificar un recurso único (ej: /articulo?id=5) es menos RESTful y puede causar problemas con caching y legibilidad. Usa parámetros de ruta para recursos específicos (/articulo/5). Inversamente, usar parámetros de ruta para filtros opcionales (ej: /articulos/filtro/valor) crea una explosión de rutas y es inflexible. Reserva los parámetros de consulta para operaciones de filtrado, ordenación y paginación sobre colecciones.
3. Olvidar que mux.Vars Devuelve Strings: El mapa devuelto por mux.Vars siempre contiene valores de tipo string. Un error común es intentar usarlos directamente como números o otros tipos, lo que provocará errores de compilación o lógica. La conversión explícita y el manejo del error de conversión son obligatorios.
4. No Aprovechar las Restricciones (Matchers) en las Rutas: Definir rutas como /articulos/{id} sin restricciones hará que coincidan con /articulos/abc o /articulos/123-xyz, forzando a tu handler a manejar formatos inválidos. Aplica restricciones desde el enrutador usando expresiones regulares (ej: {id:[0-9]+} para IDs numéricos, {slug:[a-z-]+} para slugs). Esto descarta solicitudes mal formadas de manera más eficiente y limpia tu código handler.
5. Manejo Inadecuado de Parámetros de Consulta Multi-Valor: Usar solo Query().Get() recupera el primer valor para una clave. Si tu API soporta múltiples valores para un mismo filtro (ej: ?categoria=libros&categoria=peliculas), debes usar Query()["categoria"] que devuelve un slice de strings. Ignorar esto puede hacer que tu API procese incorrectamente las solicitudes del cliente.
Checklist de Dominio
Para verificar que has comprendido y puedes aplicar los conceptos de esta lección, asegúrate de poder realizar las siguientes tareas:
- Definir una ruta en gorilla/mux que capture un parámetro de ruta obligatorio, como el ID de un usuario.
- Escribir un handler que utilice correctamente mux.Vars para extraer un parámetro de ruta y lo convierta de string a un tipo de dato numérico (int, int64) con validación de error.
- Construir un endpoint que acepte y procese múltiples parámetros de consulta (query parameters) para filtrar o paginar una lista de recursos.
- Aplicar una restricción de expresión regular a un parámetro de ruta en su definición (por ejemplo, limitar un ID a solo dígitos).
- Diferenciar claramente en un diseño de API cuándo usar un parámetro de ruta y cuándo usar un parámetro de consulta para un caso de uso dado.
- Manejar correctamente el caso en el que un parámetro de consulta puede tener múltiples valores (ej: ?tags=go&tags=api).
- Implementar una validación básica de los valores obtenidos tanto de parámetros de ruta como de consulta (presencia, formato, rango).
- Explicar la diferencia práctica entre el comportamiento de r.URL.Query().Get("clave") y r.URL.Query()["clave"].
Conclusión Práctica: El manejo experto de parámetros y consultas es lo que transforma un conjunto de endpoints estáticos en una API dinámica y poderosa. Gorilla/mux te proporciona las herramientas para hacerlo con elegancia y control. Recuerda siempre que la validación es tu mejor aliada: nunca confíes en la entrada del usuario. Combina las restricciones a nivel de ruta con una validación robusta en tus handlers para construir APIs que no solo sean funcionales, sino también resilientes y seguras.Falar no WhatsApp