Node.js Framework

Koa

Koa est une réécriture minimaliste d'Express basée sur async/await, conçue par ses auteurs originaux. Un objet de contexte et un modèle de middleware en oignon — tout le reste est un package que vous choisissez.

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);
Sortie
2013
Créé par
L'équipe Express
S'exécute sur
Node.js
Style
Minimal, async/await
Idée centrale
Middleware en oignon
Routage
@koa/router
Langage
JavaScript / TypeScript

Pourquoi c'est important

Pourquoi Koa existe

Un cœur assez petit pour être lu

Koa ne livre presque rien : pas de routeur, pas de body parser, pas de serveur de fichiers statiques. Vous ajoutez exactement les middleware dont vous avez besoin, ce qui maintient une surface de dépendances minuscule et un flux de contrôle visible.

L'oignon, pas une chaîne plate

Les middleware sont des fonctions async qui appellent await next(). Le code avant l'appel s'exécute à l'entrée, le code après s'exécute à la sortie ; ainsi, le timing, le logging et les transactions enveloppent l'intégralité de la requête.

Des middleware que vous choisissez vraiment

Tout ce qui dépasse le cœur est un package. Choisissez un routeur, un parser, un logger et un store de session, puis composez-les sans qu'un framework ne décide du reste pour vous.

Le tableau complet

Contexte, oignon, async

Un seul objet contient la requête et la réponse, les middleware s'enveloppent mutuellement, et async/await pilote le flux.

Contexte

Unifier

Un seul objet ctx fusionne la requête et la réponse. ctx.body, ctx.status, ctx.params et ctx.state remplacent la manipulation fastidieuse de req et res.

Oignon

Composer

Chaque middleware est une fonction async (ctx, next) => {}. L'appel à await next() passe le contrôle en aval ; le code qui suit s'exécute une fois que tout ce qui est en aval a terminé.

Async

Flux

Koa a été conçu pour les promesses dès le départ, donc les erreurs sont des exceptions ordinaires qui remontent vers un seul gestionnaire au lieu d'être silencieusement ignorées.

Un bref aperçu

Un petit framework qui a influencé les autres

  1. 2013

    Annonce de Koa

    TJ Holowaychuk et l'équipe Express lancent un framework minuscule basé sur les fonctions génératrices et la bibliothèque co.

    13
  2. 2014

    Stabilisation de l'objet contexte

    L'objet ctx fusionné et le modèle de middleware en oignon deviennent la structure que Koa utilise encore aujourd'hui.

    14
  3. 2017

    Koa 2 et async/await

    Node 7.6 introduit async/await nativement, et Koa 2 abandonne les générateurs pour des promesses classiques.

    17
  4. 2019

    @koa/router

    Le package koa-router, présent depuis longtemps, est renommé et confié à la communauté sous le nom de @koa/router.

    19
  5. Aujourd'hui

    Discret et influent

    Koa conserve une API petite et stable tandis que des frameworks plus récents empruntent ses idées de middleware async.

    Aujourd'hui

Le guide complet

Koa: Tout ce que vous devez savoir

Qu’est-ce que Koa ?

Koa est un framework web minimaliste pour Node.js conçu par la même équipe qu’Express. Alors qu’Express a développé un vaste écosystème de middleware et l’API req/res bien connue, Koa a repris les bases avec deux objectifs : un cœur léger et un support de premier ordre pour async/await. Plus qu’un concurrent d’Express, Koa est une réflexion délibérée sur sa conception.

L’auteur original d’Express, TJ Holowaychuk, a créé Koa pour corriger ce qui semblait maladroit dans Express à l’époque : les callbacks imbriqués, la gestion d’erreurs ad hoc et un cœur devenu trop volumineux. Koa 1 utilisait des fonctions génératrices. Koa 2 est arrivé après que Node 7.6 a introduit nativement async/await, et c’est cette version que vous utiliserez aujourd’hui.

Si Express vous enseigne la chaîne de middleware, Koa vous montre ce qui se passe lorsque cette chaîne peut envelopper une requête au lieu de simplement la traverser.

L’objet context

Koa fusionne la requête et la réponse en un seul objet appelé le context, conventionnellement nommé ctx. Au lieu de lire req et d’écrire dans res, vous lisez et écrivez des propriétés sur un seul et même objet.

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

La requête et la réponse restent intégralement disponibles lorsque vous en avez besoin :

  • ctx.request — le message entrant encapsulé (ctx.request.body, ctx.request.header).
  • ctx.response — le message sortant encapsulé (ctx.response.status).
  • ctx.params, ctx.query et ctx.request.body — le routage et les entrées analysées.
  • ctx.state — un objet simple pour transmettre des valeurs entre middleware, comme l’utilisateur authentifié.
  • ctx.throw(status, message) — pour lever une erreur HTTP que le middleware d’erreur pourra intercepter.

ctx.state est l’endroit idiomatique pour attacher des données partagées. Le middleware d’authentification définit ctx.state.user, puis les middleware suivants ou la route le lisent, sans polluer l’objet de requête lui-même.

L’oignon : le middleware qui enveloppe

Un middleware Koa est une simple fonction async avec la signature (ctx, next). Appeler await next() transfère le contrôle vers l’intérieur ; tout ce que vous écrivez après cette ligne sera exécuté une fois que les couches internes auront terminé leur traitement.

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

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

C’est le modèle en oignon, et c’est l’idée fondamentale qui rend Koa unique. Une chaîne plate ne peut exécuter du code qu’avant la réponse ; l’oignon peut exécuter du code des deux côtés.

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

  await next(); // everything downstream runs here

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

Ce middleware unique chronomètre l’intégralité de la requête, y compris chaque route et chaque autre middleware enregistré en dessous de lui. Ce même modèle est utilisé pour la journalisation (logging), les transactions de base de données et le nettoyage.

Un middleware peut également court-circuiter la chaîne : s’il définit ctx.body et n’appelle jamais next(), la requête s’arrête là. C’est ainsi que l’authentification rejette une requête avant qu’elle n’atteigne une route.

Le routage avec @koa/router

Koa ne possède pas de routeur intégré, le routage est donc assuré par @koa/router, le package maintenu par la communauté.

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() monte les gestionnaires correspondants, et router.allowedMethods() répond avec le 405 approprié lorsque le chemin existe mais que la méthode ne correspond pas. Les routeurs peuvent être imbriqués et préfixés, ce qui permet de garder une API volumineuse modulaire, tout comme le fait express.Router().

Gestion des erreurs et app.on(“error”)

Comme les middleware Koa sont des fonctions async ordinaires, les erreurs sont des exceptions classiques. Interceptez-les en un seul endroit en encapsulant l’appel descendant.

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);
  }
});

À l’intérieur d’un handler, ctx.throw(404, "not found") crée une erreur avec un status, un message et un flag expose qui indique s’il est sûr de l’afficher aux clients. Les erreurs sans statut deviennent des 500, et leur message est masqué dans la réponse.

Toutes les erreurs ne peuvent pas être transformées en réponse. Si une panne survient après l’envoi des headers, Koa émet alors un événement error sur l’app :

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

Abonnez-vous systématiquement à cet événement afin que les pannes imprévues soient toujours journalisées.

Pourquoi Koa n’a pas de routeur ou de body parser intégré

Le cœur de Koa est intentionnellement presque vide. Il vous fournit le contexte, le pipeline de middleware et la plomberie HTTP ; le routage, le parsing du corps de requête (body parsing), les cookies, les sessions, les fichiers statiques et les headers de sécurité résident tous dans des packages séparés.

C’est un compromis délibéré. Un cœur minimaliste est facile à auditer, possède peu de dépendances et évolue lentement, ce qui limite les risques que Koa lui-même ne casse votre application. La contrepartie est que vous êtes responsable de la composition : c’est à vous de décider quel body parser ajouter, comment le configurer et où l’enregistrer.

import bodyParser from "koa-bodyparser";

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

L’ordre est ici tout aussi important que dans Express. Un parser enregistré après le routeur ne sera pas disponible pour les routes.

Koa ou Express ?

Les deux frameworks partagent les mêmes concepts de requête/réponse, le choix repose donc principalement sur une question de philosophie.

Choisissez Express lorsque la familiarité est prioritaire : c’est le framework Node.js le plus utilisé, il inclut nativement le routage et possède la plus vaste collection de middleware. C’est l’option par défaut la plus sûre pour une équipe qui souhaite avancer rapidement.

Choisissez Koa si vous préférez un cœur plus léger et le modèle en “oignon” (onion model). La gestion des erreurs asynchrones y est plus propre, l’objet de contexte évite bien des manipulations entre req/res, et vous n’installez que les middleware dont vous avez réellement besoin. En contrepartie, l’écosystème est plus restreint et demande plus de configuration manuelle de votre part.

Tester une application Koa

Comme une application Koa est un pipeline de middleware, elle est facile à tester avec supertest. Exportez l’application et passez app.callback() à l’assistant de requête afin qu’aucun port ne soit ouvert.

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);
});

Comme pour Express, gardez la définition de l’application séparée de app.listen() pour que les tests puissent l’importer sans démarrer de serveur.

Bonnes pratiques

  • Placez le middleware de gestion des erreurs en premier afin qu’il englobe toutes les autres couches.
  • Utilisez ctx.state pour les valeurs liées à la requête, comme l’utilisateur authentifié.
  • Définissez ctx.body et ctx.status plutôt que de manipuler directement la réponse brute.
  • Enregistrez bodyParser avant le routeur pour que ctx.request.body soit renseigné.
  • Utilisez ctx.throw pour les erreurs HTTP attendues et un gestionnaire unique pour toutes les autres.
  • Abonnez-vous toujours à app.on("error") pour journaliser les échecs qui surviennent après l’envoi des headers.
  • Exportez l’application séparément du serveur pour garantir la rapidité des tests.

Erreurs courantes

  • Appeler next() deux fois dans un même middleware, ce qui exécute à nouveau le code en aval.
  • Oublier await avant next(), ce qui court-circuite la phase de “retour” de l’oignon.
  • Enregistrer le routeur avant le body parser et constater que ctx.request.body est vide.
  • Supposer que Koa possède un routeur ou un body parser intégré et importer le mauvais package.
  • Intercepter des erreurs sans les propager, ne laissant aucune trace dans les logs.
  • Muter ctx.res directement, contournant ainsi la gestion des statuts et des headers de Koa.

Et après ?

Koa est la démonstration la plus pure du fonctionnement des middleware asynchrones dans Node.js, et son modèle en “oignon” se retrouve dans des frameworks de tous les langages. Si vous recherchez un ensemble de fonctionnalités intégrées plus large et plus de performance, consultez le guide Fastify. Si vous préférez le framework dont Koa est issu, redécouvrez Express. Pour comprendre la couche HTTP sous-jacente à ctx, commencez par le guide HTTP, tout en gardant les bases de Node.js à portée de main.

En pratique

L'oignon en code

Basculez entre les onglets pour voir comment les middleware, le routage et les erreurs s'articulent.

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`);
}

L'ordre de l'oignon

Les middleware s'exécutent de haut en bas à l'entrée et de bas en haut à la sortie. Enregistrer le logger en dernier signifie qu'il n'enveloppe que ce qui vient après lui.

Préférer
app.use(timing);          // wraps everything below
app.use(logger);
app.use(router.routes());
Éviter
app.use(router.routes());
app.use(timing);          // never runs for matched routes

Répondre à une requête

Définissez ctx.body et laissez Koa écrire la réponse. Utiliser la réponse brute de Node court-circuite la gestion du statut, des headers et des erreurs de Koa.

Préférer
ctx.status = 201;
ctx.body = post;
Éviter
ctx.res.statusCode = 201;
ctx.res.end(JSON.stringify(post));

Compromis

Koa est-il la bonne base pour votre API ?

Koa vous offre un cœur petit et élégant et vous laisse gérer le reste. C'est à la fois une force et un coût.

Strengths

  • Un cœur facile à appréhender

    Le framework ne fait que quelques centaines de lignes. Vous pouvez lire le code source et savoir exactement comment une requête circule du socket à la réponse.

  • L'oignon est réellement utile

    Envelopper chaque requête avec du timing, du logging ou une transaction de base de données devient trivial car le code après await next() s'exécute à la sortie.

  • Gestion propre des erreurs async

    Comme les middleware sont des fonctions async, une erreur levée est capturée par le try/catch le plus proche au lieu de disparaître dans un rejet non géré.

Trade-offs

  • C'est à vous d'assembler la stack

    Le routage, le parsing du corps, les cookies et les fichiers statiques sont tous des packages séparés. Prévoyez du temps pour les choisir, les configurer et les maintenir compatibles.

  • Un écosystème plus restreint

    Il y a moins de middleware spécifiques à Koa qu'à Express, bien que la plupart des packages Express possèdent un wrapper Koa léger ou un équivalent direct.

  • Moins de directives pour les grosses apps

    Koa n'impose aucune opinion sur la structure. Les équipes doivent s'accorder sur des conventions rapidement, sinon les grands projets dérivent vers des gestionnaires incohérents.

FAQ

Foire aux questions

Keep learning

Related topics from the roadmap.

$ commencer à apprendre

Prêt à apprendre Koa ?

Notre tutoriel interactif vous guide à travers Koa pas à pas — avec des quiz et du vrai code que vous pouvez exécuter dans le navigateur.