La biblioteca estándar de Go incluye un servidor HTTP completo y listo para producción en el paquete net/http. No necesitas ningún framework externo para construir una API REST funcional, gestionar rutas con parámetros, escribir middleware o hacer peticiones como cliente HTTP.
Servidor básico con HandleFunc
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hola desde Go")
})
http.HandleFunc("/salud", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
})
fmt.Println("Servidor en :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
ServeMux propio para evitar el mux global
El mux global de http es conveniente para scripts rápidos, pero en producción es mejor usar un ServeMux propio para evitar efectos secundarios de paquetes que registren handlers en el global:
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /usuarios", listarUsuarios)
mux.HandleFunc("POST /usuarios", crearUsuario)
mux.HandleFunc("GET /usuarios/{id}", obtenerUsuario)
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}
Desde Go 1.22, el mux estándar admite métodos en la ruta (GET /ruta) y parámetros con {nombre}.
Handlers como tipos
type UsuariosHandler struct {
db *sql.DB
}
func (h *UsuariosHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.listar(w, r)
case http.MethodPost:
h.crear(w, r)
default:
http.Error(w, "método no permitido", http.StatusMethodNotAllowed)
}
}
func (h *UsuariosHandler) listar(w http.ResponseWriter, r *http.Request) {
// consultar h.db y responder
}
mux.Handle("/usuarios", &UsuariosHandler{db: db})
Middleware
Un middleware en Go es una función que envuelve un http.Handler y devuelve otro:
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
fmt.Printf("%s %s %vn", r.Method, r.URL.Path, time.Since(start))
})
}
func auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if !validarToken(token) {
http.Error(w, "no autorizado", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// aplicar middlewares
handler := logging(auth(mux))
Leer y escribir JSON
type Producto struct {
ID int `json:"id"`
Nombre string `json:"nombre"`
Precio float64 `json:"precio"`
}
func crearProducto(w http.ResponseWriter, r *http.Request) {
var p Producto
if err := json.NewDecoder(r.Body).Decode(&p); err != nil {
http.Error(w, "JSON inválido", http.StatusBadRequest)
return
}
// procesar p...
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(p)
}
http.Client: hacer peticiones
func obtenerJSON(url string, destino any) error {
cliente := &http.Client{Timeout: 10 * time.Second}
resp, err := cliente.Get(url)
if err != nil {
return fmt.Errorf("GET %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status inesperado: %d", resp.StatusCode)
}
return json.NewDecoder(resp.Body).Decode(destino)
}
Siempre configura un Timeout en el cliente. El http.DefaultClient no tiene timeout y puede bloquear las goroutines indefinidamente.
