wandres.dev
FRAMEWORKS EN WORKERS · Hono y full-stack

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.

⏱ 15 min

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.

🎯 Al terminar esta lección sabrás
  • Entender qué resuelve Hono y por qué pesa tan poco.
  • Definir rutas con app.get y app.post, y leer el objeto Context.
  • Encadenar middleware con app.use y 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 no es la Request cruda

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);
});
Ergonomía sin traicionar el átomo

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.

⚔️ Levanta un Worker con Hono
  1. Crea un proyecto con Hono y escribe tres rutas: un GET / que devuelva texto, un GET /users/:id que lea el param y un POST /users que parsee el body con await c.req.json().
  2. Añade un middleware con app.use("*", ...) que mida el tiempo de respuesta y lo escriba en una cabecera; comprueba que el código tras await next() corre a la vuelta.
  3. Tipa la app con new Hono<{ Bindings: Env }>() y lee un binding real desde c.env dentro de un handler.
  4. Explica en voz alta por qué export default app basta para desplegar, apoyándote en que una instancia de Hono ya expone un método fetch.