En esta página
HTTP y APIs REST
HTTP y APIs REST en Go: la biblioteca estándar es suficiente
Una de las características más sorprendentes de Go es que su biblioteca estándar incluye un servidor HTTP completo, production-grade. Empresas como Google, Cloudflare y Dropbox han construido sistemas que manejan millones de requests por segundo usando net/http sin frameworks externos.
Go 1.22 mejoró significativamente el ServeMux estándar con soporte para método HTTP en el pattern y path parameters, eliminando la necesidad de routers de terceros para la mayoría de casos de uso.
El servidor HTTP más simple
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "¡Hola, Go HTTP!")
})
// ListenAndServe bloquea hasta que el servidor se detiene
http.ListenAndServe(":8080", nil) // usa el DefaultServeMux global
}`http.Request`: accediendo a los datos del request
func handler(w http.ResponseWriter, r *http.Request) {
// Método HTTP
fmt.Println("Método:", r.Method) // GET, POST, PUT, DELETE, etc.
// URL y path
fmt.Println("URL:", r.URL)
fmt.Println("Path:", r.URL.Path)
fmt.Println("Query:", r.URL.RawQuery)
// Query parameters
q := r.URL.Query()
nombre := q.Get("nombre") // ?nombre=Ana
página := q.Get("página") // ?página=2
_ = nombre
_ = página
// Headers
token := r.Header.Get("Authorization")
contentType := r.Header.Get("Content-Type")
_ = token
_ = contentType
// Body (para POST/PUT)
defer r.Body.Close()
// Leer directamente
datos, err := io.ReadAll(r.Body)
_ = err
_ = datos
}`http.ResponseWriter`: escribir respuestas
func handler(w http.ResponseWriter, r *http.Request) {
// Headers deben escribirse ANTES del status y el body
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Custom-Header", "valor")
// Status code (solo se puede escribir una vez)
w.WriteHeader(http.StatusCreated) // 201
// Body
w.Write([]byte(`{"mensaje": "creado"}`))
// Helpers convenientes de http
http.Error(w, "No encontrado", http.StatusNotFound)
http.Redirect(w, r, "/nueva-ruta", http.StatusMovedPermanently)
}JSON: codificación y decodificación
import "encoding/json"
type Usuario struct {
ID int `json:"id"`
Nombre string `json:"nombre"`
Email string `json:"email,omitempty"`
}
// Codificar struct → JSON
func escribirJSON(w http.ResponseWriter, status int, v any) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
return json.NewEncoder(w).Encode(v)
}
// Decodificar JSON → struct
func leerJSON(r *http.Request, v any) error {
defer r.Body.Close()
// Limitar tamaño del body a 1MB
r.Body = http.MaxBytesReader(nil, r.Body, 1<<20)
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() // rechazar campos desconocidos
return decoder.Decode(v)
}ServeMux de Go 1.22+: pattern matching avanzado
mux := http.NewServeMux()
// Patrón con método HTTP (nuevo en Go 1.22)
mux.HandleFunc("GET /usuarios", listarUsuarios)
mux.HandleFunc("POST /usuarios", crearUsuario)
mux.HandleFunc("GET /usuarios/{id}", obtenerUsuario)
mux.HandleFunc("PUT /usuarios/{id}", actualizarUsuario)
mux.HandleFunc("DELETE /usuarios/{id}", eliminarUsuario)
// Acceder a path parameters
func obtenerUsuario(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // r.PathValue disponible desde Go 1.22
// ...
}
// Subtree routing (el / al final captura cualquier subruta)
mux.HandleFunc("/static/", servirArchivosEstáticos)
// Host-specific routing
mux.HandleFunc("api.mi-app.com/v1/", apiV1Handler)Middleware: funciones que envuelven handlers
El patrón de middleware en Go es simple: una función que recibe un http.Handler y retorna otro http.Handler:
// Tipo de middleware
type Middleware func(http.Handler) http.Handler
// Middleware de autenticación
func autenticar(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "No autorizado", http.StatusUnauthorized)
return
}
// Verificar token...
next.ServeHTTP(w, r)
})
}
// Middleware de CORS
func cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
// Middleware de logging con slog (Go 1.21+)
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
rw := &responseWriter{ResponseWriter: w, status: http.StatusOK}
next.ServeHTTP(rw, r)
slog.Info("http",
"método", r.Method,
"ruta", r.URL.Path,
"status", rw.status,
"duración", time.Since(inicio),
)
})
}
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(status int) {
rw.status = status
rw.ResponseWriter.WriteHeader(status)
}
// Encadenar middlewares
func encadenar(h http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}API REST completa: estructura de un proyecto real
// handlers/usuario.go
type UsuarioHandler struct {
repo *UsuarioRepositorio
log *slog.Logger
}
func (h *UsuarioHandler) Listar(w http.ResponseWriter, r *http.Request) {
usuarios, err := h.repo.Todos(r.Context())
if err != nil {
h.log.Error("error listando usuarios", "error", err)
writeError(w, http.StatusInternalServerError, "error interno")
return
}
writeJSON(w, http.StatusOK, usuarios)
}
func (h *UsuarioHandler) Crear(w http.ResponseWriter, r *http.Request) {
var req struct {
Nombre string `json:"nombre"`
Email string `json:"email"`
}
if err := leerJSON(r, &req); err != nil {
writeError(w, http.StatusBadRequest, "JSON inválido: "+err.Error())
return
}
if req.Nombre == "" || req.Email == "" {
writeError(w, http.StatusUnprocessableEntity, "nombre y email son requeridos")
return
}
usuario, err := h.repo.Crear(r.Context(), req.Nombre, req.Email)
if err != nil {
writeError(w, http.StatusInternalServerError, "error al crear usuario")
return
}
writeJSON(w, http.StatusCreated, usuario)
}
// main.go
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
repo := NewUsuarioRepositorio()
handler := &UsuarioHandler{repo: repo, log: logger}
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/usuarios", handler.Listar)
mux.HandleFunc("POST /api/v1/usuarios", handler.Crear)
mux.HandleFunc("GET /api/v1/usuarios/{id}", handler.ObtenerPorID)
srv := &http.Server{
Addr: ":8080",
Handler: encadenar(mux, logging, cors, recuperarPanico),
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
}
logger.Info("Servidor iniciado", "addr", srv.Addr)
if err := srv.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
logger.Error("error del servidor", "error", err)
os.Exit(1)
}
}Sirviendo archivos estáticos
// Servir archivos desde un directorio
mux.Handle("GET /static/", http.StripPrefix("/static/",
http.FileServer(http.Dir("./public"))))
// Usando embed para archivos incrustados en el binario
import "embed"
//go:embed public
var archivosEstáticos embed.FS
mux.Handle("GET /static/", http.FileServerFS(archivosEstáticos))Con net/http dominado, estás listo para el proyecto final: construirás un gestor de tareas CLI con API HTTP que aplica todo lo aprendido en el curso.
Inicia sesión para guardar tu progreso