Node.js Framework

Koa

Koa es la versión minimalista de Express basada en async/await, escrita por sus autores originales. Un único objeto de contexto y una cebolla de middleware: todo lo demás es un paquete que tú eliges.

intermediate14 min readUpdated 16 sept 2026
app.js
js
// app.js
import Koa from "koa";
import Router from "@koa/router";

const app = new Koa();
const router = new Router();

router.get("/users/:id", async (ctx) => {
  const user = await db.user.findById(ctx.params.id);
  if (!user) ctx.throw(404, "user not found");
  ctx.body = user;
});

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  ctx.set("X-Response-Time", `${Date.now() - start}ms`);
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(3000);
Lanzado
2013
Creado por
El equipo de Express
Ejecuta sobre
Node.js
Estilo
Minimalista, async/await
Idea central
Onion middleware
Enrutamiento
@koa/router
Lenguaje
JavaScript / TypeScript

Por que importa

Por qué existe Koa

Un núcleo lo suficientemente pequeño para leerse

Koa casi no incluye nada: ni router, ni body parser, ni servidor de archivos estáticos. Añades exactamente el middleware que necesitas, lo que mantiene la superficie de dependencias mínima y el flujo de control visible.

La cebolla, no una cadena plana

Los middleware son funciones asíncronas que llaman a await next(). El código antes de la llamada se ejecuta al entrar, y el código después de ella se ejecuta al salir; así, el timing, el logging y las transacciones envuelven la solicitud completa.

Middleware que tú eliges realmente

Todo lo que está más allá del núcleo es un paquete. Elige un router, un parser, un logger y un almacén de sesiones, y luego compónlos sin que un framework decida el resto por ti.

La imagen completa

Contexto, cebolla, async

Un solo objeto contiene la solicitud y la respuesta, los middleware se envuelven entre sí y async/await gestiona el flujo.

Contexto

Unificar

Un único objeto ctx fusiona la solicitud y la respuesta. ctx.body, ctx.status, ctx.params y ctx.state reemplazan el malabarismo entre req y res.

Cebolla

Componer

Cada middleware es async (ctx, next) => {}. Llamar a await next() cede el control hacia abajo; el código posterior se ejecuta una vez que todo lo siguiente ha terminado.

Async

Flujo

Koa fue construido para promesas desde el principio, por lo que los errores son excepciones ordinarias que suben hasta un manejador en lugar de desaparecer silenciosamente.

Una breve historia

Un framework pequeño que influyó en el resto

  1. 2013

    Se anuncia Koa

    TJ Holowaychuk y el equipo de Express lanzan un framework diminuto basado en funciones generadoras y la librería co.

    13
  2. 2014

    Se asienta el objeto de contexto

    El objeto ctx fusionado y el modelo de middleware en cebolla se convierten en la estructura que Koa sigue utilizando hoy en día.

    14
  3. 2017

    Koa 2 y async/await

    Node 7.6 introduce async/await nativo, y Koa 2 abandona los generadores en favor de promesas simples.

    17
  4. 2019

    @koa/router

    El veterano paquete koa-router es renombrado y entregado a la comunidad como @koa/router.

    19
  5. Hoy

    Silencioso e influyente

    Koa mantiene una API pequeña y estable mientras frameworks más nuevos toman prestadas sus ideas de middleware asíncronos.

    Hoy

La guia completa

Koa: Todo lo que necesitas saber

¿Qué es Koa?

Koa es un framework web minimalista para Node.js escrito por el mismo equipo detrás de Express. Mientras que Express desarrolló un ecosistema de middleware extenso y la familiar API req/res, Koa comenzó de cero con dos objetivos: un núcleo diminuto y soporte de primera clase para async/await. Más que un competidor de Express, es un replanteamiento deliberado del mismo.

El autor original de Express, TJ Holowaychuk, creó Koa para solucionar lo que resultaba incómodo de Express en aquel momento: los callbacks anidados, el manejo de errores improvisado y un núcleo que había acumulado más funcionalidades de las que sus autores deseaban. Koa 1 utilizaba funciones generadoras. Koa 2 llegó después de que Node 7.6 introdujera async/await nativos, y esa es la versión que utilizarás hoy en día.

Si Express te enseña la cadena de middleware, Koa te enseña qué sucede cuando se permite que esa cadena envuelva una solicitud en lugar de simplemente pasar a través de ella.

El objeto de contexto

Koa fusiona la solicitud y la respuesta en un único objeto llamado contexto, convencionalmente nombrado ctx. En lugar de leer de req y escribir en res, lees y escribes propiedades en un solo objeto.

app.use(async (ctx) => {
  ctx.status = 200;               // response status
  ctx.type = "application/json";  // response content type
  ctx.body = { ok: true };        // response body
});

La solicitud y la respuesta siguen estando disponibles por completo cuando las necesites:

  • ctx.request — el mensaje entrante envuelto (ctx.request.body, ctx.request.header).
  • ctx.response — el mensaje saliente envuelto (ctx.response.status).
  • ctx.params, ctx.query y ctx.request.body — enrutamiento y entrada procesada.
  • ctx.state — un objeto plano para pasar valores entre middleware, como el usuario autenticado.
  • ctx.throw(status, message) — lanza un error HTTP que el middleware de errores puede capturar.

ctx.state es el lugar idiomático para adjuntar datos compartidos. El middleware de autenticación establece ctx.state.user, y posteriormente el middleware o la ruta lo lee, sin contaminar el objeto de solicitud en sí.

La cebolla: middleware que envuelve

El middleware de Koa es una única función async con la firma (ctx, next). Llamar a await next() transfiere el control hacia el interior; cualquier cosa que escribas después de esa línea se ejecutará una vez que las capas internas hayan terminado.

request  ──▶  mw1 before ──▶  mw2 before ──▶  route

response ◀──  mw1 after  ◀──  mw2 after  ◀────────┘

Este es el modelo de cebolla, y es la idea fundamental que hace que Koa sea distintivo. Una cadena plana solo puede ejecutar código antes de la respuesta; la cebolla puede ejecutar código en ambos lados.

app.use(async (ctx, next) => {
  const start = Date.now();

  await next(); // everything downstream runs here

  ctx.set("X-Response-Time", `${Date.now() - start}ms`);
});

Ese único middleware mide el tiempo de toda la solicitud, incluyendo cada ruta y cualquier otro middleware registrado debajo de él. El mismo patrón se utiliza para el logging, las transacciones de base de datos y la limpieza.

Un middleware también puede interrumpir la cadena: si establece ctx.body y nunca llama a next(), la solicitud termina ahí. Así es como la autenticación rechaza una solicitud antes de que llegue a una ruta.

Enrutamiento con @koa/router

Koa no incluye un router en su núcleo, por lo que el enrutamiento se proporciona a través de @koa/router, el paquete mantenido por la comunidad.

import Koa from "koa";
import Router from "@koa/router";

const app = new Koa();
const router = new Router();

router.get("/posts", async (ctx) => {
  ctx.body = await Post.find().limit(20);
});

router.get("/posts/:id", async (ctx) => {
  const post = await Post.findById(ctx.params.id);
  if (!post) ctx.throw(404, "post not found");
  ctx.body = post;
});

app.use(router.routes());
app.use(router.allowedMethods());

router.routes() monta los handlers correspondientes, y router.allowedMethods() responde con el 405 correcto cuando la ruta existe pero el método no. Los routers pueden anidarse y llevar prefijos, lo que permite mantener una API extensa de forma modular, de la misma manera que lo hace express.Router().

Manejo de errores y app.on(“error”)

Dado que los middleware de Koa son funciones async ordinarias, los errores son excepciones comunes. Puedes capturarlos en un solo lugar envolviendo la llamada downstream.

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status ?? 500;
    ctx.body = { error: err.expose ? err.message : "internal_error" };
    ctx.app.emit("error", err, ctx);
  }
});

Dentro de un handler, ctx.throw(404, "not found") crea un error con un status, un mensaje y un flag expose que indica si es seguro mostrarlo a los clientes. Los errores sin un status se convierten en 500, y su mensaje se oculta de la respuesta.

No todos los errores pueden convertirse en una respuesta. Si ocurre un fallo después de que se hayan enviado los headers, Koa emitirá un evento error en la app:

app.on("error", (err, ctx) => {
  console.error(`${ctx.method} ${ctx.url}`, err);
});

Suscríbete a ese evento sin falta, para que los fallos inesperados siempre queden registrados en los logs.

Por qué Koa no tiene un router o body parser integrados

El núcleo de Koa está intencionalmente casi vacío. Te proporciona el contexto, el pipeline de middleware y la infraestructura HTTP; el routing, el body parsing, las cookies, las sesiones, los archivos estáticos y los encabezados de seguridad residen en paquetes separados.

Este es un intercambio deliberado. Un núcleo pequeño es fácil de auditar, tiene pocas dependencias y evoluciona lentamente, por lo que es difícil que Koa en sí mismo rompa tu aplicación. El costo es que tú eres el responsable de la composición: tú decides qué body parser añadir, cómo parsearlo y dónde registrarlo.

import bodyParser from "koa-bodyparser";

app.use(bodyParser());
app.use(router.routes());
app.use(router.allowedMethods());

Aquí el orden es fundamental, al igual que sucede en Express. Un parser registrado después del router no estará disponible para las rutas.

¿Koa o Express?

Ambos frameworks comparten las mismas ideas de solicitud/respuesta, por lo que la elección depende principalmente de la filosofía.

Elige Express cuando la familiaridad sea lo más importante: es el framework de Node.js más utilizado, incluye routing y posee la colección de middleware más amplia. Es la opción más segura por defecto para un equipo que necesite avanzar rápidamente.

Elige Koa cuando busques un núcleo más ligero y el modelo de cebolla (onion model). El manejo de errores asíncronos es más limpio, el objeto de contexto elimina gran parte del malabarismo entre req/res y solo pagas por el middleware que añadas. El intercambio es un ecosistema más reducido y que te corresponda hacer más ensamblaje.

Probando una app de Koa

Debido a que una app de Koa es un pipeline de middleware, es sencillo probarla con supertest. Exporta la app y pasa app.callback() al helper de peticiones para que no se abra ningún puerto.

import request from "supertest";
import app from "../app.js";

test("GET /posts returns a list", async () => {
  const res = await request(app.callback()).get("/posts").expect(200);
  expect(Array.isArray(res.body)).toBe(true);
});

Al igual que con Express, mantén la definición de la app separada de app.listen() para que los tests puedan importarla sin iniciar un servidor.

Mejores prácticas

  • Mantén el middleware de manejo de errores al principio para que envuelva a todas las demás capas.
  • Usa ctx.state para valores con alcance de solicitud (request-scoped), como el usuario autenticado.
  • Establece ctx.body y ctx.status en lugar de manipular la respuesta raw.
  • Registra bodyParser antes del router para que ctx.request.body se complete.
  • Usa ctx.throw para errores HTTP previstos y un único manejador para el resto.
  • Suscríbete siempre a app.on("error") para registrar fallos que ocurran después de enviar los headers.
  • Exporta la app independientemente del servidor para que los tests sigan siendo rápidos.

Errores comunes

  • Llamar a next() dos veces en un mismo middleware y ejecutar el código downstream nuevamente.
  • Olvidar await antes de next(), lo que omite la mitad de “salida” de la cebolla.
  • Registrar el router antes que el body parser y descubrir que ctx.request.body está vacío.
  • Asumir que Koa tiene un router o un body parser integrado e importar el paquete incorrecto.
  • Silenciar errores sin emitirlos, dejando los logs vacíos.
  • Mutar ctx.res directamente y saltarse el manejo de estados y cabeceras de Koa.

Próximos pasos

Koa es la demostración más limpia de middleware async en Node, y su modelo de cebolla (onion model) está presente en frameworks de prácticamente todos los lenguajes. Si buscas un conjunto de funcionalidades integradas más amplio y mayor velocidad, lee la guía de Fastify. Si prefieres el framework del cual surgió Koa, vuelve a consultar Express. Para comprender la capa HTTP que hay debajo de ctx, comienza con la guía de HTTP y ten a mano los conceptos básicos de Node.js.

En la practica

La cebolla en código

Cambia entre las pestañas para ver cómo encajan el middleware, el enrutamiento y los errores.

middleware/timing.js
export async function timing(ctx, next) {
  const start = Date.now();

  // runs on the way in
  await next();

  // runs on the way out, after every downstream middleware
  const ms = Date.now() - start;
  ctx.set("X-Response-Time", `${ms}ms`);
}

El orden de la cebolla

Los middleware se ejecutan de arriba a abajo al entrar y de abajo a arriba al salir. Registrar el logger al final significa que solo envuelve lo que viene después de él.

Preferido
app.use(timing);          // wraps everything below
app.use(logger);
app.use(router.routes());
Evitar
app.use(router.routes());
app.use(timing);          // never runs for matched routes

Responder a una solicitud

Establece ctx.body y deja que Koa escriba la respuesta. Recurrir a la respuesta raw de Node omite el manejo de estados, cabeceras y errores de Koa.

Preferido
ctx.status = 201;
ctx.body = post;
Evitar
ctx.res.statusCode = 201;
ctx.res.end(JSON.stringify(post));

Compromisos

¿Es Koa la base adecuada para tu API?

Koa te ofrece un núcleo pequeño y elegante y deja el resto en tus manos. Eso es tanto una fortaleza como un coste.

Strengths

  • Un núcleo que puedes retener en tu cabeza

    El framework tiene unas pocas cientos de líneas. Puedes leer el código fuente y saber exactamente cómo fluye una solicitud desde el socket hasta la respuesta.

  • La cebolla es genuinamente útil

    Envolver cada solicitud con timing, logging o una transacción de base de datos se vuelve trivial porque el código después de await next() se ejecuta al salir.

  • Manejo de errores asíncronos limpio

    Como los middleware son funciones asíncronas, un error lanzado es capturado por el try/catch más cercano en lugar de desaparecer en un rechazo no manejado.

Trade-offs

  • Tú ensamblas el stack

    El enrutamiento, el body parsing, las cookies y los archivos estáticos son paquetes separados. Reserva tiempo para elegirlos, conectarlos y mantenerlos compatibles.

  • Un ecosistema más pequeño

    Hay menos middleware específicos de Koa que de Express, aunque la mayoría de los paquetes de Express tienen un wrapper ligero para Koa o un equivalente directo.

  • Menos guía para aplicaciones grandes

    Koa no tiene opiniones sobre la estructura. Los equipos deben acordar convenciones pronto o los proyectos grandes derivarán en manejadores inconsistentes.

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender Koa?

Nuestro tutorial interactivo te guia a traves de Koa paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.