chi: Enrutamiento Ligero y Middleware
chi añade enrutamiento consciente de métodos, parámetros de URL y composición de middleware sobre net/http sin introducir un tipo de manejador personalizado.
Busca en todas las páginas de la documentación
chi añade enrutamiento consciente de métodos, parámetros de URL y composición de middleware sobre net/http sin introducir un tipo de manejador personalizado.
Los manejadores siguen siendo func(http.ResponseWriter, *http.Request), por lo que chi se adapta a equipos que desean la ergonomía de un enrutador con portabilidad de stdlib.
chi construye un árbol de rutas (radix tree) en tiempo de registro y despacha las solicitudes entrantes a través de una pila de middleware antes de que se ejecute tu manejador.
Los grupos de rutas (Route, Group, Mount) te permiten delimitar middleware y prefijos a subárboles.
Dado que chi.Router implementa http.Handler, puedes insertarlo en http.Server, pruebas de integración y proxies inversos de la misma manera que lo harías con un ServeMux simple.
Tarjeta de receta de referencia rápida, lista para copiar y pegar.
r := chi.NewRouter()
r.Use(middleware.RequestID, middleware.Recoverer)
r.Route("/api", func(r chi.Router) {
r.Get("/users/{id}", getUser)
})
http.ListenAndServe(":8080", r)Cuándo usar esto:
http.Handler de stdlib (OTel, pasarelas de autenticación).http.Handler para que los consumidores lo monten.http.ServeMux manteniendo los manejadores existentes.package main
import (
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
type user struct {
ID int `json:"id"`
Name string `json:"name"`
}
var users = map[int]user{1: {ID: 1, Name: "Ada"}}
func getUser(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(chi.URLParam(r, "id"))
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
u, ok := users[id]
if !ok {
http.Error(w, "not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(u)
}
func main() {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(30 * time.Second))
r.Route("/api/v1", func(r chi.Router) {
r.Get("/users/{id}", getUser)
r.Post("/users", func(w http.ResponseWriter, r *http.Request) {
var in user
if err := json.NewDecoder(r.Body).Decode(&in); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
users[in.ID] = in
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(in)
})
})
srv := &http.Server{Addr: ":8080", Handler: r}
srv.ListenAndServe()
}Lo que esto demuestra:
/api/v1.chi.NewRouter() asigna un árbol de rutas; cada Get/Post registra un nodo de método + patrón.Use envuelve la cadena de manejadores del enrutador actual; los sub-enrutadores heredan el middleware padre a menos que se registren antes de una rama.Mount("/prefix", sub) elimina el prefijo y delega la coincidencia a sub.http.NotFound a menos que configures r.NotFound y r.MethodNotAllowed.| Patrón | Coincide | Acceso al parámetro |
|---|---|---|
/users/{id} | /users/42 | chi.URLParam(r, "id") |
/files/{path:*} | /files/a/b/c | segmento rest codicioso |
/health | ruta exacta | ninguno |
El middleware externo se ejecuta primero al entrar y el último al salir.
Registra el logging y la recuperación temprano; coloca la autenticación después del logging para que las solicitudes rechazadas aún dejen un rastro de auditoría.
El middleware de tiempo de espera debe ubicarse fuera de los manejadores que puedan bloquear.
import "context"
// Middleware personalizado: adjunta un valor al contexto, estilo stdlib
func withTenant(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tenant := r.Header.Get("X-Tenant")
type tenantKeyType struct{}
ctx := context.WithValue(r.Context(), tenantKeyType{}, tenant)
next.ServeHTTP(w, r.WithContext(ctx))
})
}Prefiere claves de contexto tipadas (tipo entero personalizado) sobre cadenas vacías para evitar colisiones.
r.URL.Path crudo confunde a los operadores. Solución: registra chi.RouteContext(r).RoutePattern o incluye el prefijo de montaje en campos estructurados.Use después de Mount no envuelve retroactivamente los subárboles montados registrados anteriormente. Solución: registra Use compartido en el padre antes de Mount, o añade middleware en cada sub-enrutador.chi.URLParam devuelve string; olvidar strconv produce IDs 0 silenciosos. Solución: analiza y valida antes de las llamadas al dominio.http.ListenAndServe usa tiempos de espera cero; el middleware Timeout de chi ayuda pero no reemplaza ReadHeaderTimeout en http.Server. Solución: configura explícitamente los tiempos de espera de http.Server./users a /users/ dependiendo del registro; los clientes inconsistentes pueden golpear dos veces. Solución: elige un estilo y registra ambos o deshabilita las redirecciones conscientemente.http.HandlerFunc.| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
ServeMux de stdlib (1.22+) | Dependencias mínimas, patrones simples | Necesitas grupos de rutas y pilas de middleware ricas |
| gorilla/mux | Coincidencia de expresiones regulares/host/encabezados | Quieres velocidad de características activa y rendimiento de radix |
| Gin/Echo | Vinculación JSON pesada y middleware de framework | Requiere firmas http.Handler puras en todas partes |
httprouter directamente | Máxima velocidad bruta, API mínima | Quieres middleware empaquetado y grupos de rutas |
Sí, muchas APIs de producción usan chi como una capa delgada sobre net/http con configuración explícita del servidor y middleware de observabilidad.
Construye un enrutador, llama a rr := httptest.NewRecorder(), req := httptest.NewRequest("GET", "/api/v1/users/1", nil), luego r.ServeHTTP(rr, req).
Usa http.FileServer envuelto con StripPrefix, o middleware.Compress más FileServer montado en un sub-enrutador.
Escribe una pequeña utilidad que establezca Content-Type, estado y codifique una estructura; evita esparcir http.Error con texto plano en APIs JSON.
Usa el middleware github.com/go-chi/cors o tu propio manejador Access-Control-* al principio de la cadena.
chi no genera especificaciones automáticamente; mantén OpenAPI por separado o usa herramientas de generación de código que acepten tablas de rutas que documentes.
Route y Group crean sub-enrutadores que heredan el middleware Use padre registrado antes del bloque del grupo.
Monta el http.Handler del gateway con r.Mount y mantén gRPC en un oyente separado o usa cmux con propiedad clara.
chi se mantiene activamente, utiliza un árbol de rutas rápido y coincide idiomáticamente con los patrones HTTP modernos de Go.
middleware.RequestID establece el encabezado; léelo del contexto o de los envoltorios del escritor de respuesta en el middleware descendente.
Sí, pasa el enrutador como Handler; configura TLS en http.Server como de costumbre.
Establece r.MethodNotAllowed a un manejador personalizado; el comportamiento predeterminado devuelve 405 cuando la ruta coincide pero el verbo no.
Versiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado GC de Green Tea, go fix modernizers - verificar parche en la compilación), chi (última versión - verificar en la compilación), gin (última versión - verificar en la compilación), echo (última versión - verificar en la compilación), google.golang.org/grpc (última versión - verificar en la compilación), sigs.k8s.io/controller-runtime (última versión - verificar en la compilación), kubebuilder (última versión - verificar en la compilación), tinygo (última versión - verificar objetivos de placa en la compilación), wazero (última versión - verificar en la compilación) y golangci-lint (última versión - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 16 jul 2026