Definición de rutas y manejo de verbos HTTP

Lectura
20 min~5 min lectura

Concepto clave

En el desarrollo de APIs REST con Go y gorilla/mux, las rutas y verbos HTTP son los cimientos de tu arquitectura. Piensa en una ruta como una dirección específica en tu servidor (como /api/users) y los verbos HTTP (GET, POST, PUT, DELETE) como las acciones que puedes realizar en esa dirección. gorilla/mux es un enrutador HTTP que extiende las capacidades del router estándar de Go, permitiéndote definir rutas con parámetros, restricciones y manejar diferentes métodos HTTP de forma más expresiva.

Una analogía del mundo real sería un restaurante: las rutas son las mesas disponibles (/mesa/1, /mesa/2), y los verbos HTTP son lo que haces en ellas: GET para ver el menú, POST para hacer un pedido, PUT para modificar el pedido, y DELETE para cancelarlo. gorilla/mux actúa como el maître que dirige cada solicitud a la mesa correcta y asegura que solo se permitan acciones válidas.

Cómo funciona en la práctica

Para implementar rutas con gorilla/mux, primero importas el paquete y creas un nuevo router. Luego, defines rutas usando métodos como HandleFunc o Methods para asociar verbos HTTP específicos. Por ejemplo, para crear un endpoint que maneje usuarios, podrías definir:

  1. Una ruta /api/users que responda a GET (listar usuarios) y POST (crear usuario).
  2. Una ruta /api/users/{id} con un parámetro dinámico para GET (obtener un usuario), PUT (actualizar) y DELETE (eliminar).

El router de gorilla/mux procesa las solicitudes entrantes, extrae parámetros de la URL (como {id}), y las pasa a la función manejadora correspondiente. Esto te permite construir APIs RESTful de manera clara y mantenible, separando la lógica de enrutamiento de la lógica de negocio.

Código en acción

Aquí tienes un ejemplo básico de cómo definir rutas y manejar verbos HTTP con gorilla/mux. Este código crea un servidor simple con endpoints para gestionar usuarios.

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "github.com/gorilla/mux"
)

type User struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

var users = []User{{ID: "1", Name: "Alice"}, {ID: "2", Name: "Bob"}}

func getUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(users)
}

func createUser(w http.ResponseWriter, r *http.Request) {
    var newUser User
    if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    users = append(users, newUser)
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(newUser)
}

func main() {
    r := mux.NewRouter()

    // Definir rutas y asociar verbos HTTP
    r.HandleFunc("/api/users", getUsers).Methods("GET")
    r.HandleFunc("/api/users", createUser).Methods("POST")

    // Ruta con parámetro dinámico
    r.HandleFunc("/api/users/{id}", func(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
        id := vars["id"]
        for _, user := range users {
            if user.ID == id {
                json.NewEncoder(w).Encode(user)
                return
            }
        }
        http.Error(w, "User not found", http.StatusNotFound)
    }).Methods("GET")

    fmt.Println("Servidor escuchando en http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", r))
}

Ahora, mejoremos el código refactorizando el manejo de rutas con parámetros para hacerlo más limpio y reutilizable. Antes, teníamos la lógica inline; después, la extraemos a una función dedicada.

// Función mejorada para manejar GET por ID
func getUserByID(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id := vars["id"]
    for _, user := range users {
        if user.ID == id {
            w.Header().Set("Content-Type", "application/json")
            json.NewEncoder(w).Encode(user)
            return
        }
    }
    http.Error(w, "User not found", http.StatusNotFound)
}

// En main, actualizamos la ruta
func main() {
    r := mux.NewRouter()
    r.HandleFunc("/api/users", getUsers).Methods("GET")
    r.HandleFunc("/api/users", createUser).Methods("POST")
    r.HandleFunc("/api/users/{id}", getUserByID).Methods("GET")
    // Podemos agregar más métodos fácilmente, ej: r.HandleFunc("/api/users/{id}", updateUser).Methods("PUT")
    fmt.Println("Servidor escuchando en http://localhost:8080")
    log.Fatal(http.ListenAndServe(":8080", r))
}

Errores comunes

  • No validar métodos HTTP: Definir rutas sin especificar .Methods() puede llevar a que un endpoint responda a verbos no deseados, causando comportamientos inesperados. Siempre asocia verbos explícitamente.
  • Manejo incorrecto de parámetros de ruta: Olvidar extraer parámetros con mux.Vars(r) o no manejar casos donde el parámetro no existe, resultando en errores 500. Valida siempre la existencia de parámetros.
  • Falta de encabezados Content-Type: No establecer Content-Type: application/json en respuestas JSON puede causar problemas en clientes. Inclúyelo consistentemente.
  • Rutas duplicadas o conflictivas: Definir rutas similares (ej: /api/users y /api/users/{id}) en órdenes incorrectas puede hacer que una anule a otra. Ordena rutas de más específicas a más generales.
  • Ignorar el cierre del cuerpo de la solicitud: No cerrar r.Body después de leerlo en manejadores POST/PUT puede llevar a fugas de memoria. Usa defer r.Body.Close() o maneja el error adecuadamente.

Checklist de dominio

  1. Puedo crear un router con gorilla/mux y definir rutas básicas para una API REST.
  2. Sé cómo asociar verbos HTTP específicos (GET, POST, PUT, DELETE) a rutas usando .Methods().
  3. Manejo parámetros dinámicos en rutas (ej: {id}) y los extrao correctamente en mis manejadores.
  4. Configuro encabezados HTTP apropiados, como Content-Type para respuestas JSON.
  5. Valido y manejo errores en solicitudes entrantes, devolviendo códigos de estado HTTP correctos.
  6. Estructuro mis rutas para evitar conflictos, ordenándolas de específicas a generales.
  7. Refactorizo código para separar la lógica de enrutamiento de la lógica de negocio, mejorando mantenibilidad.

Construye un API REST para gestionar productos

En este ejercicio, aplicarás lo aprendido para crear un microservicio en Go que gestione productos usando gorilla/mux. Sigue estos pasos:

  1. Crea un nuevo proyecto Go e instala gorilla/mux con go get github.com/gorilla/mux.
  2. Define una estructura Product con campos: ID (string), Name (string), Price (float64).
  3. Inicializa un slice en memoria para almacenar productos, con al menos dos productos de ejemplo.
  4. Configura un router con gorilla/mux y define las siguientes rutas RESTful:
    • GET /api/products: Devuelve la lista de todos los productos en JSON.
    • GET /api/products/{id}: Devuelve un producto específico por ID, o 404 si no existe.
    • POST /api/products: Crea un nuevo producto a partir de JSON en el cuerpo de la solicitud, valida que el ID no esté duplicado, y devuelve el producto creado con estado 201.
    • PUT /api/products/{id}: Actualiza un producto existente por ID, devolviendo el producto actualizado o 404 si no existe.
  5. Asegúrate de que cada ruta use el verbo HTTP correcto y maneje errores apropiados (ej: JSON inválido devuelve 400).
  6. Ejecuta el servidor en localhost:8080 y prueba los endpoints con una herramienta como curl o Postman.
Pistas
  • Usa mux.Vars(r) para acceder al ID en rutas con parámetros, y valida que exista antes de procesar.
  • En POST, decodifica el JSON del cuerpo con json.NewDecoder y maneja posibles errores de decodificación.
  • Para evitar duplicados en POST, verifica que el ID del nuevo producto no exista ya en tu slice antes de agregarlo.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.