Go

Go

Tutorial Go: API REST con net/http

Aprende a implementar APIs REST en Go usando net/http. Configura rutas, gestiona peticiones y utiliza JSON sin frameworks, optimizando tu aplicación web.

Aprende Go GRATIS y certifícate

Configurar un API REST JSON sin framework

Para configurar un API REST en Go sin emplear un framework externo, se utiliza el paquete estándar net/http, que proporciona todas las herramientas necesarias para manejar peticiones HTTP. La clave está en definir handlers que respondan a las rutas específicas de nuestro API.

Primero, se debe crear un nuevo ServerMux utilizando http.NewServeMux(). Este objeto actúa como un enrutador que asigna las rutas a sus respectivos handlers. El uso de NewServeMux permite aprovechar características avanzadas, como el routing basado en métodos y el soporte de comodines en patrones de URL, introducido en versiones recientes de Go.

mux := http.NewServeMux()

Para manejar peticiones de distintas rutas, se utiliza HandleFunc o Handle, donde puedes definir rutas tanto estáticas como dinámicas. Con el routing basado en métodos, se puede especificar directamente el método HTTP en el patrón de la URL, mejorando la seguridad y precisión del diseño del API.

mux.HandleFunc("POST /items", createItemHandler)
mux.HandleFunc("GET /items", itemsHandler)
mux.HandleFunc("GET /items/{id}", itemHandler)
mux.HandleFunc("GET /items/{id}/details/{details...}", itemDetailsHandler)

Este ejemplo muestra cómo definir un manejador para crear un ítem, otro para recuperar un ítem específico y un tercer manejador que utiliza un comodín de múltiples segmentos para obtener detalles adicionales del ítem. Este enfoque permite que los handlers respondan únicamente a los métodos HTTP previstos, reduciendo el riesgo de manejar peticiones no deseadas.

Finalmente, se inicia el servidor HTTP vinculando el ServerMux configurado al puerto deseado. Esto se realiza con http.ListenAndServe, que comienza a escuchar y servir las peticiones entrantes.

http.ListenAndServe(":8080", mux)

Este enfoque permite implementar un API REST JSON, aprovechando las capacidades del paquete estándar de Go sin la necesidad de frameworks adicionales.

package main

import (
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    mux.HandleFunc("POST /items", createItemHandler)
    mux.HandleFunc("GET /items", itemsHandler)
    mux.HandleFunc("GET /items/{id}", itemHandler)
    mux.HandleFunc("GET /items/{id}/details/{details...}", itemDetailsHandler)

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

Recibir peticiones GET

En el desarrollo de un API REST utilizando Go, una de las operaciones más comunes es recibir peticiones GET para obtener recursos. La biblioteca estándar net/http proporciona un marco robusto para manejar estas peticiones. Para gestionar las peticiones GET, se utiliza el ServerMux configurado previamente, que permite definir rutas específicas y asociarlas a funciones handler.

En una aplicación típica, se define un handler que procesa las peticiones GET. Este handler es una función que recibe un http.ResponseWriter y un *http.Request como argumentos. El ResponseWriter se utiliza para enviar respuestas HTTP al cliente, mientras que el objeto Request contiene todos los detalles de la petición entrante.

Obtener todos los ítems

Para mostrar todos los ítems, se puede definir un handler adicional que responda a la ruta GET /items y devuelva la lista completa en formato JSON:

type Item struct {
    ID       string   `json:"id"`
    Name     string   `json:"name"`
    Features []string `json:"features"`
}

var items = []Item{
    {ID: "1", Name: "Ítem 1", Features: []string{"Feature A", "Feature B"}},
    {ID: "2", Name: "Ítem 2", Features: []string{"Feature C", "Feature D"}},
}

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

En este ejemplo, el handler itemsHandler simplemente codifica y devuelve el arreglo items completo en formato JSON. Esta es una operación común en APIs REST para listar todos los recursos disponibles de un tipo específico.

Recuperar un ítem específico

Además de listar todos los ítems, se puede recuperar un ítem específico utilizando su identificador único a partir de una ruta GET /items/{id}:

func itemHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    id := r.URL.PathValue("id")

    for _, item := range items {
        if item.ID == id {
            json.NewEncoder(w).Encode(item)
            return
        }
    }

    http.Error(w, "Ítem no encontrado", http.StatusNotFound)
}

En este handler, se busca el ítem correspondiente en el arreglo items usando el identificador id extraído de la URL. Si se encuentra, se envía la representación JSON del ítem al cliente. Si no se encuentra, se devuelve un error 404 Not Found.

Manejo de rutas con número indefinido de segmentos

Para ilustrar cómo manejar un número indefinido de segmentos en una ruta, se puede definir una ruta adicional que permita acceder a detalles específicos del ítem utilizando múltiples segmentos en la URL. Por ejemplo, la ruta /items/{id}/details/{details...} puede capturar múltiples segmentos adicionales que representan diferentes aspectos o categorías de detalles del ítem.

mux.HandleFunc("GET /items/{id}/details/{details...}", itemDetailsHandler)

Definimos el handler correspondiente que procesará estos segmentos adicionales:

func itemDetailsHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    id := r.PathValue("id")
    detailsPath := r.PathValue("details")

    // Buscar el ítem por ID
    var foundItem *Item
    for _, item := range items {
            if item.ID == id {
                    foundItem = &item
                    break
            }
    }

    if foundItem == nil {
            http.Error(w, "Ítem no encontrado", http.StatusNotFound)
            return
    }

    // Procesar los detalles adicionales
    // Por ejemplo, dividir los detallesPath en segmentos
    // Supongamos que detallesPath = "featureA/subfeature1"
    detailsSegments := splitPath(detailsPath)

    // Crear una respuesta que incluya los detalles solicitados
    response := map[string]interface{}{
            "id":       foundItem.ID,
            "name":     foundItem.Name,
            "features": foundItem.Features,
            "details":  detailsSegments,
    }

    json.NewEncoder(w).Encode(response)
}

// Función auxiliar para dividir el path en segmentos
func splitPath(path string) []string {
    var segments []string
    current := ""
    for _, char := range path {
            if char == '/' {
                    if current != "" {
                            segments = append(segments, current)
                            current = ""
                    }
            } else {
                    current += string(char)
            }
    }
    if current != "" {
            segments = append(segments, current)
    }
    return segments
}

En este handler itemDetailsHandler, se captura el parámetro id y el parámetro details que puede contener múltiples segmentos separados por barras (/). La función auxiliar splitPath divide el segmento detailsPath en partes individuales para procesarlos según sea necesario. En este ejemplo, simplemente se incluyen en la respuesta para ilustrar cómo se pueden manejar múltiples segmentos.

Uso del ejemplo de parámetros indefinidos:

Supongamos que se realiza una petición GET a la siguiente URL:

GET http://localhost:8080/items/1/details/featureA/subfeature1

El handler itemDetailsHandler extraerá:

  • id: "1"
  • details: "featureA/subfeature1"

Después de procesar, la respuesta JSON podría verse así:

{
    "id": "1",
    "name": "Ítem 1",
    "features": ["Feature A", "Feature B"],
    "details": ["featureA", "subfeature1"]
}

Recibir peticiones POST

En el desarrollo de APIs REST en Go, las peticiones POST son esenciales para crear recursos en el servidor. Utilizando el paquete estándar net/http, es posible manejar estas peticiones. La clave para procesar peticiones POST es definir un handler adecuado que pueda interpretar el cuerpo de la petición y realizar las operaciones necesarias.

Para comenzar, se configura el ServerMux, que actúa como enrutador, mapeando las rutas a sus respectivos handlers. El routing basado en métodos permite asociar directamente el método HTTP a la ruta, garantizando que el handler responda únicamente a peticiones POST.

mux.HandleFunc("POST /items", createItemHandler)

El handler para una petición POST es una función que recibe un http.ResponseWriter y un *http.Request. El objeto Request contiene el cuerpo de la petición, al que se puede acceder y leer para obtener los datos enviados por el cliente. Generalmente, estos datos se envían en formato JSON y deben ser decodificados en una estructura Go.

func createItemHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    var item Item
    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&item)
    if err != nil {
        http.Error(w, "Solicitud incorrecta", http.StatusBadRequest)
        return
    }

    // Asignar un ID sencillo basado en la longitud del arreglo
    item.ID = fmt.Sprintf("%d", len(items)+1)

    // Agregar el nuevo ítem al arreglo
    items = append(items, item)

    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(item)
}

En este ejemplo, el handler createItemHandler utiliza un json.Decoder para deserializar el cuerpo de la petición en una estructura Item. Después de decodificar, asigna un ID sencillo al ítem basado en la longitud actual del arreglo items y lo agrega al arreglo. Finalmente, responde al cliente con un código de estado 201 Created y la representación JSON del ítem creado.

Realizar peticiones HTTP PUT y PATCH

El manejo de peticiones PUT y PATCH en una API REST con Go es esencial para permitir la modificación de recursos existentes. Utilizando el paquete net/http, podemos implementar estas operaciones. Las peticiones PUT generalmente se utilizan para reemplazar un recurso completo, mientras que PATCH se enfoca en modificar partes específicas del recurso.

Para comenzar, se configura un ServerMux que mapea las rutas a sus respectivos handlers, especificando el método HTTP directamente en el patrón de la URL. Esto asegura que cada handler responda únicamente al método esperado, aumentando la seguridad de la API.

mux.HandleFunc("PUT /items/{id}", updateItemHandler)
mux.HandleFunc("PATCH /items/{id}", patchItemHandler)

Manejo de peticiones PUT

El handler para una petición PUT debe ser capaz de manejar el reemplazo completo del recurso. Se espera que el cliente envíe la representación completa del recurso en el cuerpo de la petición, que debe ser decodificada en una estructura Go.

func updateItemHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    var updatedItem Item
    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&updatedItem)
    if err != nil {
        http.Error(w, "Solicitud incorrecta", http.StatusBadRequest)
        return
    }

    id := r.PathValue("id")
    updatedItem.ID = id

    // Buscar y reemplazar el ítem en el arreglo
    for i, item := range items {
        if item.ID == id {
            items[i] = updatedItem
            json.NewEncoder(w).Encode(updatedItem)
            return
        }
    }

    http.Error(w, "Ítem no encontrado", http.StatusNotFound)
}

En este ejemplo, el handler updateItemHandler decodifica el cuerpo de la petición en una estructura Item. Luego, asigna el ID extraído de la URL al ítem actualizado y busca el ítem correspondiente en el arreglo items. Si lo encuentra, reemplaza el ítem existente con el nuevo y responde con la representación JSON del ítem actualizado. Si no se encuentra el ítem, responde con un error 404 Not Found.

Manejo de peticiones PATCH

Para las peticiones PATCH, el enfoque es similar, pero se centra en actualizar solo los campos especificados. Esto requiere lógica adicional para fusionar las actualizaciones con el recurso existente.

func patchItemHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")

    var updates map[string]interface{}
    decoder := json.NewDecoder(r.Body)
    err := decoder.Decode(&updates)
    if err != nil {
        http.Error(w, "Solicitud incorrecta", http.StatusBadRequest)
        return
    }

    id := r.PathValue("id")

    for i, item := range items {
        if item.ID == id {
            // Aplicar actualizaciones
            if name, ok := updates["name"].(string); ok {
                items[i].Name = name
            }
            if features, ok := updates["features"].([]interface{}); ok {
                var newFeatures []string
                for _, feature := range features {
                    if f, ok := feature.(string); ok {
                        newFeatures = append(newFeatures, f)
                    }
                }
                items[i].Features = newFeatures
            }
            // Agregar más campos según sea necesario

            json.NewEncoder(w).Encode(items[i])
            return
        }
    }

    http.Error(w, "Ítem no encontrado", http.StatusNotFound)
}

En patchItemHandler, se decodifica el cuerpo de la petición en un mapa de actualizaciones parciales. Luego, busca el ítem correspondiente en el arreglo items y aplica las actualizaciones especificadas. En este ejemplo, se actualizan los campos name y features si están presentes en la solicitud. Finalmente, responde con la representación JSON del ítem parcialmente actualizado. Si no se encuentra el ítem, responde con un error 404 Not Found.

Realizar peticiones HTTP DELETE

El manejo de peticiones HTTP DELETE en un API REST desarrollado con Go es crucial para permitir la eliminación de recursos. Utilizando el paquete estándar net/http, podemos implementar estas operaciones, aprovechando las capacidades avanzadas de enrutamiento de ServerMux.

Para empezar, se debe configurar un ServerMux que asocie las rutas a sus respectivos handlers, especificando el método HTTP directamente en el patrón de la URL. Esto garantiza que cada handler responda únicamente al método DELETE esperado, lo que incrementa la seguridad del API.

mux.HandleFunc("DELETE /items/{id}", deleteItemHandler)

El handler para una petición DELETE es una función que recibe un http.ResponseWriter y un *http.Request. En este contexto, el handler debe ser capaz de interpretar el identificador del recurso que se desea eliminar, el cual se extrae de la ruta usando la capacidad de comodines de ServerMux.

func deleteItemHandler(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")

    for i, item := range items {
        if item.ID == id {
            // Eliminar el ítem del arreglo
            items = append(items[:i], items[i+1:]...)
            w.WriteHeader(http.StatusNoContent)
            return
        }
    }

    http.Error(w, "Ítem no encontrado", http.StatusNotFound)
}

En este ejemplo, el handler deleteItemHandler utiliza r.PathValue("id") para obtener el parámetro dinámico id de la URL, que identifica el recurso a eliminar. Recorre el arreglo items para encontrar el ítem correspondiente y lo elimina utilizando la función append para rebanar el arreglo. Finalmente, responde al cliente con un código de estado 204 No Content para indicar que la eliminación fue exitosa y que no hay contenido adicional que devolver. Si no se encuentra el ítem, responde con un error 404 Not Found.

Aprende Go GRATIS online

Todas las lecciones de Go

Accede a todas las lecciones de Go y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Go y certifícate

Certificados de superación de Go

Supera todos los ejercicios de programación del curso de Go y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Configurar un API REST en Go sin frameworks.
  • Manejar rutas HTTP utilizando ServerMux.
  • Implementar solicitudes HTTP GET, POST, PUT, PATCH y DELETE.
  • Gestionar rutas dinámicas y múltiples segmentos.
  • Procesar y responder en formato JSON.
  • Asegurar APIs utilizando enrutamiento basado en métodos.