Hono: el router ultraligero del edge
Hono es el framework de facto para escribir Workers con rutas y middleware sin renunciar al modelo de la plataforma. Qué resuelve app.get, cómo funciona el objeto Context, por qué su middleware sigue el patron de la cebolla y qué lo hace encajar tan bien en el edge que la propia Cloudflare lo pone de ejemplo.
Enrutar a mano dentro de un fetch handler funciona para tres rutas, pero degenera en un nido de condicionales en cuanto la aplicación crece. Hono es la respuesta madura a ese problema en el edge: un router diminuto, sin dependencias y construido sobre los estándares web, que te regala app.get, middleware y un objeto de contexto ergonómico sin alejarte ni un milímetro del contrato Request a Response. No es casualidad que sea el framework que Cloudflare enseña una y otra vez en su documentación.
- Entender qué resuelve Hono y por qué pesa tan poco.
- Definir rutas con
app.getyapp.post, y leer el objetoContext. - Encadenar middleware con
app.usey el patrón de la cebolla. - Explicar por qué su diseño encaja de forma natural en Workers.
Un router que cabe en un suspiro
Hono —炎, “llama” en japonés— nació con una obsesión: ser el router más rápido y ligero posible para entornos donde cada kilobyte y cada milisegundo de arranque cuentan. El núcleo pesa menos que un puñado de kilobytes y no arrastra ni una sola dependencia. Esa frugalidad no es coquetería: en el edge, el tamaño del bundle influye en lo que tarda un isolate en cargar tu código, y las dependencias transitivas son deuda que pagas en cada despliegue.
La velocidad de enrutado viene de su RegExpRouter, que compila todas tus rutas en una sola expresión regular y resuelve la coincidencia sin recorrer una lista ruta por ruta. Sobre esa base, la API pública es tan mínima que se aprende en una tarde:
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hola desde el edge"));
export default app;
Fíjate en la última línea. No envuelves nada, no escribes un fetch handler a mano: export default app basta porque una instancia de Hono es ya un objeto con un método fetch compatible con el runtime. El framework no sustituye el contrato de la plataforma, lo implementa por ti.
Sin dependencias
El núcleo no arrastra librerías externas. Menos bundle, arranque más rápido del isolate y una superficie de auditoría mínima.
Estándares web
Trabaja con Request y Response de la WHATWG, las mismas de un Worker. Lo que aprendes aquí no queda atrapado en la plataforma.
Router veloz
El RegExpRouter resuelve la ruta con una única expresión regular precompilada, no con un barrido lineal de la tabla.
Multi-runtime
El mismo código corre en Workers, Deno, Bun y Node. El edge es su hogar, pero no su cárcel.
app.get y el objeto Context
Cada método HTTP tiene su verbo: app.get, app.post, app.put, app.delete. El primer argumento es el patrón de ruta —con parámetros dinámicos al estilo /users/:id— y el segundo es el handler, una función que recibe un único objeto llamado por convención c, el Context.
Ese Context es la pieza central de la ergonomía de Hono. Unifica en un solo objeto la petición entrante, la respuesta que construyes, los bindings de la plataforma y un almacén por petición. Leer la entrada y fabricar la salida se vuelve declarativo:
app.get("/users/:id", (c) => {
const id = c.req.param("id"); // parametro de ruta
const orden = c.req.query("orden"); // ?orden=asc de la query
return c.json({ id, orden }); // Response con Content-Type JSON
});
app.post("/users", async (c) => {
const cuerpo = await c.req.json(); // parsea el body como JSON
return c.json({ creado: cuerpo }, 201);
});
Los ayudantes c.text, c.json y c.html no son magia: cada uno construye una Response con el Content-Type correcto y el estado que le indiques. Bajo ellos siguen estando el objeto Response estándar y el mismo contrato de siempre; Hono solo te ahorra escribir a mano las cabeceras que ya sabes de memoria.
c.req es un HonoRequest, una envoltura fina sobre la Request estándar con ayudantes como param, query y valid. Si necesitas el objeto crudo —para pasarlo a otra API o clonarlo— lo tienes en c.req.raw. La envoltura añade comodidad sin esconderte el estándar que hay debajo.
Middleware: el patrón de la cebolla
Un middleware es una función que se ejecuta antes o después de tu handler y que puede inspeccionar o modificar la petición y la respuesta. Se registra con app.use y sigue el patrón de la cebolla: cada middleware envuelve al siguiente, de modo que el código anterior a await next() corre en el camino de entrada y el posterior corre en el de salida, en orden inverso.
import { logger } from "hono/logger";
app.use("*", logger()); // middleware de librería, para toda ruta
app.use("*", async (c, next) => {
const inicio = Date.now();
await next(); // cede el control hacia dentro
const ms = Date.now() - inicio; // se ejecuta al volver
c.header("X-Response-Time", `${ms}ms`);
});
La llamada await next() es la bisagra: suspende el middleware actual, ejecuta todo lo que hay más adentro —otros middleware y, al final, tu handler— y devuelve el control para que ejecutes lo que pusiste después. Por eso el orden de registro importa tanto: define en qué capa de la cebolla vive cada pieza.
flowchart LR In[Request entra] --> A1[Middleware A antes] A1 --> B1[Middleware B antes] B1 --> H[Handler app.get] H --> B2[Middleware B despues] B2 --> A2[Middleware A despues] A2 --> Out[Response sale] style H fill:#a6e3a1,color:#11111b style In fill:#89b4fa,color:#11111b
Hono trae una batería de middleware oficiales listos para el edge: cors, logger, basicAuth, cache, jwt y compresión, entre otros. Cada uno se importa desde su propio submódulo, así que solo pagas en bundle por lo que usas. Ese diseño modular es coherente con la filosofía frugal del núcleo.
Por qué encaja en el edge
La afinidad de Hono con Workers no es marketing, es arquitectura. Tres decisiones lo explican. Primero, habla el idioma nativo de la plataforma: Request entra, Response sale, sin capa de traducción entre el framework y el runtime. Segundo, su tamaño minúsculo mantiene el isolate ligero, algo que un framework pensado para un servidor de larga vida nunca prioriza. Y tercero, expone los bindings sin fricción: si tipas la aplicación con new Hono<{ Bindings: Env }>(), accedes a tus recursos con c.env.DB o c.env.MI_KV, el mismo env que recibiría un fetch handler crudo.
type Env = { DB: D1Database; CACHE: KVNamespace };
const app = new Hono<{ Bindings: Env }>();
app.get("/tarea/:id", async (c) => {
const fila = await c.env.DB
.prepare("SELECT * FROM tareas WHERE id = ?")
.bind(c.req.param("id"))
.first();
return c.json(fila);
});
Hono es el ejemplo perfecto de una abstracción que añade comodidad sin romper el modelo que hay debajo. Todo lo que te ofrece —el enrutado por verbos, el Context unificado, la cebolla de middleware— es, cuando lo destilas, código que recibe una Request y decide qué Response fabricar. El RegExpRouter es una forma inteligente de elegir qué función ejecutar según la URL; un middleware es una Response que pasa por varias manos antes de salir; c.env es el mismo llavero de bindings que la plataforma inyecta en cualquier handler. Nada de esto contradice el átomo del track: lo envuelve. Y ahí está la lección de diseño, transferible más allá de Hono: las mejores abstracciones no esconden el contrato subyacente, lo hacen ergonómico y siguen dejándote bajar a él cuando lo necesitas. Puedes tomar la Request cruda de c.req.raw, puedes devolver un new Response a pelo, puedes exportar app porque ya es un Worker válido. Hono no te pide que olvides lo que aprendiste sobre el fetch handler; te pide que dejes de reescribirlo a mano. Por eso encaja en el edge como un guante y por eso, cuando lo dominas, no has aprendido un framework más: has aprendido a construir sobre el contrato irreducible de la plataforma sin perderlo nunca de vista.
- Crea un proyecto con Hono y escribe tres rutas: un
GET /que devuelva texto, unGET /users/:idque lea elparamy unPOST /usersque parsee el body conawait c.req.json(). - Añade un middleware con
app.use("*", ...)que mida el tiempo de respuesta y lo escriba en una cabecera; comprueba que el código trasawait next()corre a la vuelta. - Tipa la app con
new Hono<{ Bindings: Env }>()y lee un binding real desdec.envdentro de un handler. - Explica en voz alta por qué
export default appbasta para desplegar, apoyándote en que una instancia de Hono ya expone un métodofetch.