Manejo de solicitudes GET y POST en Go

Video
25 min~9 min lectura
Objetivo de la lección

Introducción al Manejo de Solicitudes HTTP en Go En el desarrollo de APIs REST, las operaciones GET y POST constituyen la columna vertebral de la comunicación cliente-servidor.

Puntos de control
  • Introducción al Manejo de Solicitudes HTTP en Go
  • Concepto Clave: El Ciclo Solicitud-Respuesta y el Enrutador
  • Cómo Funciona en la Práctica: Paso a Paso
  • Código en Acción: API de Gestión de Libros

Reproductor de video

Introducción al Manejo de Solicitudes HTTP en Go

En el desarrollo de APIs REST, las operaciones GET y POST constituyen la columna vertebral de la comunicación cliente-servidor. Mientras que GET se utiliza para recuperar información, POST se emplea para crear nuevos recursos. En el ecosistema Go, el paquete net/http proporciona la funcionalidad base, pero frameworks como Gorilla/Mux nos ofrecen un enrutador rico en características que simplifica enormemente la definición de rutas complejas, la extracción de variables de la URL y la validación de esquemas de solicitud. Esta lección se centra en implementar estos verbos HTTP de manera robusta, eficiente y siguiendo las mejores prácticas para microservicios de alto rendimiento.

Dominar el manejo de estas solicitudes implica más que simplemente devolver una respuesta. Se trata de entender el ciclo de vida de la petición en Go: desde el enrutamiento y la extracción de parámetros, pasando por la validación y el procesamiento de datos, hasta la construcción de una respuesta HTTP con el código de estado, cabeceras y cuerpo adecuados. Utilizaremos Gorilla/Mux para crear un enrutador que nos permita definir rutas con parámetros, establecer métodos HTTP permitidos y aplicar middleware de manera declarativa, sentando las bases para una API escalable y mantenible.

Concepto Clave: El Ciclo Solicitud-Respuesta y el Enrutador

Imagina un gran edificio de correos (tu servidor Go) con una sala de clasificación central inteligente (el enrutador o router). Cada paquete que llega (una solicitud HTTP) tiene una etiqueta de destino (la URL) y un tipo de entrega (el método HTTP como GET o POST). El trabajo del enrutador Gorilla/Mux es examinar cada paquete entrante y decidir a qué empleado específico (nuestra función manejadora) debe enviársele para su procesamiento. Si el paquete es una solicitud GET a "/clientes/42", el enrutador la dirige al manejador de "obtener cliente", extrae el ID "42" de la etiqueta y se lo pasa. Si es un paquete POST a "/productos", lo envía al manejador de "crear producto" junto con los formularios o datos adjuntos.

La potencia de Gorilla/Mux radica en que esta "sala de clasificación" es altamente configurable. Puedes definir reglas precisas: "solo acepta paquetes POST en esta ruta si el contenido es JSON", o "extrae automáticamente el valor numérico de la ruta y conviértelo en un entero antes de pasarlo al manejador". Este proceso de enrutamiento y extracción es fundamental para construir APIs RESTful limpias y predecibles, donde los recursos (como /usuarios, /pedidos) y los verbos (GET, POST) definen claramente la operación a realizar.

Cómo Funciona en la Práctica: Paso a Paso

El proceso comienza con la creación de un nuevo router utilizando mux.NewRouter(). Este objeto será el núcleo de nuestra aplicación. A continuación, definimos las rutas utilizando métodos como router.HandleFunc(), vinculando un patrón de URL (por ejemplo, /api/articulos/{id}) a una función que recibirá los objetos http.ResponseWriter y *http.Request. Es crucial restringir el método HTTP en la ruta usando .Methods("GET") o .Methods("POST") para evitar que una ruta destinada a crear recursos reciba accidentalmente solicitudes de lectura.

Para las solicitudes GET, el flujo típico implica extraer parámetros de la ruta (con mux.Vars(r)) o de la cadena de consulta (con r.URL.Query().Get()), usar esos parámetros para consultar una fuente de datos (como una base de datos o un mapa en memoria), y luego marcar (marshal) el resultado a JSON para escribirlo en el ResponseWriter. Para las solicitudes POST, el flujo es diferente: primero debemos leer el cuerpo de la solicitud (r.Body), decodificarlo (usualmente desde JSON) a una estructura Go, validar los datos recibidos, realizar la lógica de creación, y finalmente devolver una respuesta con el código de estado HTTP 201 (Created) y, a menudo, la representación del recurso recién creado.

Un paso intermedio vital es el uso de middleware. Gorilla/Mux permite encadenar funciones que se ejecutan antes o después del manejador principal. Middleware comunes para este contexto incluyen el establecimiento de cabeceras CORS, el registro de solicitudes (logging), la compresión de respuestas, y muy importante, la decodificación automática del cuerpo JSON y la validación de esquemas. Aplicar un middleware que decodifique el JSON y valide la estructura antes de que la solicitud llegue a nuestro manejador simplifica enormemente el código y centraliza el manejo de errores de formato.

Código en Acción: API de Gestión de Libros

A continuación, construiremos un ejemplo completo y funcional de una API simple para gestionar libros, manejando solicitudes GET para listar y obtener libros, y POST para crear nuevos. Utilizaremos un mapa en memoria como almacenamiento para simplicidad.

package main

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

// Book define la estructura de nuestro recurso.
type Book struct {
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Author string `json:"author"`
    Year   int    `json:"year"`
}

// Almacenamiento en memoria. En una aplicación real, sería una base de datos.
var books = make(map[int]Book)
var idCounter = 1

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

    // Definición de rutas y sus manejadores.
    r.HandleFunc("/books", getBooks).Methods("GET")
    r.HandleFunc("/books/{id}", getBook).Methods("GET")
    r.HandleFunc("/books", createBook).Methods("POST")

    // Middleware para establecer cabecera de contenido JSON en todas las respuestas.
    r.Use(jsonContentTypeMiddleware)

    log.Println("Servidor iniciado en el puerto :8080")
    log.Fatal(http.ListenAndServe(":8080", r))
}

// jsonContentTypeMiddleware establece la cabecera Content-Type a application/json.
func jsonContentTypeMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        next.ServeHTTP(w, r)
    })
}

// getBooks maneja GET /books y devuelve la lista completa de libros.
func getBooks(w http.ResponseWriter, r *http.Request) {
    var bookList []Book
    for _, book := range books {
        bookList = append(bookList, book)
    }
    json.NewEncoder(w).Encode(bookList)
}

// getBook maneja GET /books/{id} y devuelve un libro específico.
func getBook(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
        http.Error(w, `{"error": "ID inválido"}`, http.StatusBadRequest)
        return
    }

    book, exists := books[id]
    if !exists {
        http.Error(w, `{"error": "Libro no encontrado"}`, http.StatusNotFound)
        return
    }
    json.NewEncoder(w).Encode(book)
}

// createBook maneja POST /books para crear un nuevo libro.
func createBook(w http.ResponseWriter, r *http.Request) {
    var newBook Book
    // Decodificar el cuerpo JSON de la solicitud.
    decoder := json.NewDecoder(r.Body)
    decoder.DisallowUnknownFields() // Rechaza campos no definidos en la estructura.
    err := decoder.Decode(&newBook)

    if err != nil {
        http.Error(w, `{"error": "Cuerpo de solicitud inválido"}`, http.StatusBadRequest)
        return
    }

    // Validación básica de campos requeridos.
    if newBook.Title == "" || newBook.Author == "" {
        http.Error(w, `{"error": "Los campos 'title' y 'author' son requeridos"}`, http.StatusBadRequest)
        return
    }

    // Asignar un nuevo ID y guardar el libro.
    newBook.ID = idCounter
    books[idCounter] = newBook
    idCounter++

    // Responder con el recurso creado y código 201.
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(newBook)
}

Este código demuestra un flujo completo. Observa cómo mux.Vars(r) se usa para extraer el {id} de la URL en getBook. En createBook, usamos json.NewDecoder con DisallowUnknownFields() para una decodificación más estricta y segura. El middleware jsonContentTypeMiddleware asegura que todas las respuestas tengan la cabecera correcta, demostrando cómo reutilizar lógica transversal. Las respuestas de error utilizan http.Error con un mensaje JSON consistente y el código de estado HTTP apropiado (400 para errores del cliente, 404 para recursos no encontrados).

Probando la API con cURL

Una vez ejecutes el servidor (go run main.go), puedes probarlo desde la terminal.


# 1. Crear un nuevo libro (POST)
curl -X POST http://localhost:8080/books \
  -H "Content-Type: application/json" \
  -d '{"title":"Cien años de soledad", "author":"Gabriel García Márquez", "year":1967}'

# Respuesta esperada (con ID 1):
# {"id":1,"title":"Cien años de soledad","author":"Gabriel García Márquez","year":1967}

# 2. Obtener todos los libros (GET)
curl http://localhost:8080/books

# 3. Obtener un libro específico (GET)
curl http://localhost:8080/books/1

# 4. Intentar obtener un libro inexistente (GET)
curl -v http://localhost:8080/books/999
# Debería devolver un código de estado 404.

Errores Comunes y Cómo Evitarlos

1. No Validar la Entrada del Usuario: Asumir que los datos recibidos en POST son correctos o que el ID en una ruta GET es un número válido es un error grave. Siempre valida y sanitiza. Usa strconv.Atoi con manejo de errores para IDs, y valida los campos requeridos y sus tipos en las estructuras de entrada. Considera usar librerías de validación como go-playground/validator para reglas complejas.

2. Olvidar Restringir los Métodos HTTP en las Rutas: Si defines una ruta /books sin especificar métodos, responderá a GET, POST, PUT, DELETE, etc., lo que probablemente no sea el comportamiento deseado y puede ser un riesgo de seguridad. Siempre usa .Methods("VERBO") en Gorilla/Mux para ser explícito.

3. No Establecer los Códigos de Estado HTTP Correctos: Devolver siempre 200 OK, incluso para errores o creación de recursos, es una mala práctica de API REST. Usa códigos semánticos: 200 para éxito en GET/PUT, 201 Created para POST exitoso, 400 Bad Request para datos inválidos, 404 Not Found para recursos inexistentes, y 500 Internal Server Error para fallos del servidor.

4. Leer el Cuerpo de la Solicitud (r.Body) Múltiples Veces: http.Request.Body es un io.ReadCloser que solo puede leerse una vez. Si un middleware y tu manejador intentan leerlo, el segundo fallará. La solución es usar middleware que decodifique el cuerpo y guarde el resultado en el contexto de la solicitud, o leer el cuerpo y luego crear un nuevo io.NopCloser para restablecerlo (aunque esto es menos común).

5. No Manejar el Cierre del Cuerpo de la Solicitud: Aunque no es estrictamente necesario en todos los casos, es una buena práctica cerrar el r.Body después de leerlo, especialmente en solicitudes de larga duración o bajo alta carga, para liberar recursos. Puedes usar defer r.Body.Close() al inicio del manejador.

Tip de Rendimiento: Para APIs de alto rendimiento, evita usar json.Marshal seguido de w.Write. En su lugar, utiliza json.NewEncoder(w).Encode(&struct{}). El encoder escribe directamente al ResponseWriter, reduciendo las asignaciones de memoria al evitar crear un slice de bytes intermedio. Esto es especialmente beneficioso para respuestas grandes.

Checklist de Dominio

Antes de considerar que dominas el manejo de solicitudes GET y POST con Gorilla/Mux, asegúrate de poder verificar los siguientes puntos:

  • Puedo crear un nuevo router Gorilla/Mux y definir rutas para GET y POST, restringiendo explícitamente los métodos HTTP.
  • Sé extraer y convertir correctamente parámetros de la ruta (ej: {id}) y parámetros de consulta (query strings) en mis manejadores GET.
  • Implemento la decodificación segura de cuerpos JSON en manejadores POST, con validación de campos requeridos y tipos de datos.
  • Construyo respuestas HTTP con los códigos de estado apropiados (200, 201, 400, 404, 500) y la cabecera Content-Type: application/json.
  • Puedo crear y aplicar middleware básico (como para cabeceras de contenido o logging) a mi router o a rutas específicas.
  • Comprendo la diferencia entre json.Marshal y json.NewEncoder().Encode() y sé cuándo es mejor usar cada uno.
  • Soy capaz de escribir pruebas unitarias para mis manejadores HTTP que simulen solicitudes GET y POST y verifiquen las respuestas.
  • Identifico y evito los errores comunes, como la lectura múltiple del cuerpo o la falta de validación de entrada.
Falar no WhatsApp
De lección a portfolio

Convertí esta lección en una prueba técnica visible.

Una app pequeña publicada, con README y decisiones explicadas, funciona mejor que una lista de tecnologías sueltas.

Paso 1

Creá una demo mínima que use el concepto de la lección.

Paso 2

Escribí un README corto con objetivo, stack, decisión técnica y mejora futura.

Paso 3

Publicá la demo y enlazala desde tu perfil profesional.

Newsletter Cursalo

Recibí rutas y cursos nuevos

Sumate para recibir recursos orientados a empleo y portfolio.

  • Rutas de empleo
  • Cursos prácticos
  • Portfolio y entrevistas

Sin spam. También podés entrar con tu cuenta para guardar progreso. Iniciá sesión

Manejo de solicitudes GET y POST en Go | Cursalo