Qu’est-ce que Hono ?
Hono est un framework web léger et rapide basé sur la Web Platform. Au lieu d’inventer ses propres objets de requête et de réponse, il utilise les classes standards Request et Response fournies par les navigateurs, Cloudflare Workers, Deno et Bun. Sur cette base, il ajoute le routage, des middleware et un objet de contexte, sans rien ajouter de superflu.
Son nom signifie « flamme » en japonais, et le projet mise tout sur la vitesse : un cœur minuscule, aucune dépendance native à Node et un routeur conçu pour les cold starts. Cette combinaison a fait de Hono le choix par défaut pour les API en edge, où chaque milliseconde de démarrage impacte chaque requête. Comme le contrat d’exécution repose sur les standards du web, la même application s’exécute également sur Node, vous évitant ainsi tout verrouillage propriétaire.
Le routage basé sur les standards du web
Le routage est volontairement familier. Une méthode et un chemin sont mappés vers un handler, les paramètres utilisent :name et les wildcards utilisent *.
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.text("Hello"));
app.get("/posts", (c) => c.json([]));
app.get("/posts/:id", (c) => c.json({ id: c.req.param("id") }));
app.post("/posts", (c) => c.json({ created: true }, 201));
Les routeurs peuvent être divisés en sous-applications et montés, ce qui permet aux services plus volumineux de rester organisés :
import { Hono } from "hono";
const api = new Hono();
api.get("/users", listUsers);
api.get("/users/:id", getUser);
app.route("/api", api);
Chaque handler retourne un Response. Les helpers de contexte — c.json, c.text, c.html, c.redirect, c.body — construisent la réponse correcte avec les headers et le statut, et vous pouvez toujours retourner un new Response(...) brut lorsque vous avez besoin d’un contrôle total.
L’objet context
Le seul argument du handler est le contexte, conventionnellement nommé c. Il contient la requête, les helpers de réponse ainsi qu’un espace pour stocker des valeurs propres à la requête actuelle.
app.post("/posts", async (c) => {
const id = c.req.param("id"); // path parameter
const page = c.req.query("page"); // query string
const body = await c.req.json(); // parsed body
const token = c.req.header("authorization");
c.set("requestId", crypto.randomUUID()); // per-request store
return c.json({ id, page, body, token });
});
c.env expose les bindings du runtime : variables d’environnement, namespaces KV, bases de données D1 et buckets R2 sur Workers. c.set et c.get permettent de partager des valeurs entre les middleware et les handlers, et c.var offre un accès typé à celles-ci. Tout ce dont vous avez besoin pour une requête se trouve dans un seul objet, ce qui rend la composition des middleware très simple.
Middleware
Un middleware est une fonction asynchrone qui reçoit le contexte et next. Appelez await next() pour continuer la chaîne, ou retournez une réponse anticipée pour interrompre le flux.
import { createMiddleware } from "hono/factory";
export const timing = createMiddleware(async (c, next) => {
const start = performance.now();
await next();
c.header("Server-Timing", `app;dur=${performance.now() - start}`);
});
app.use("*", timing);
L’assistant createMiddleware ajoute l’inférence de type, et app.use accepte un modèle de chemin afin que le middleware ne s’exécute que là où il est nécessaire. L’ordre d’enregistrement correspond à l’ordre d’exécution, exactement comme dans Express.
Hono propose un ensemble de middlewares utiles intégrés au cœur du framework :
corspour les en-têtes cross-origin.loggerpour la journalisation des requêtes.bearerAuthetbasicAuthpour l’authentification.cachepour la mise en cache des réponses en edge.etag,compressetsecureHeaderspour l’hygiène HTTP.csrfpour la protection contre les failles CSRF.
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { bearerAuth } from "hono/bearer-auth";
app.use("*", logger());
app.use("/api/*", cors());
app.use("/admin/*", bearerAuth({ token: c.env.ADMIN_TOKEN }));
Comme il s’agit de middlewares standards, ils se composent parfaitement avec les vôtres et avec tout autre élément de l’écosystème.
Validation et réponses typées
Hono propose des validateurs natifs pour Zod et Valibot. Ils analysent une cible (json, query, param, form), rejettent les entrées invalides avec un 400, et fournissent au handler une valeur typée.
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";
const CreatePost = z.object({
title: z.string().min(1),
body: z.string(),
});
app.post("/posts", zValidator("json", CreatePost), (c) => {
const post = c.req.valid("json");
return c.json({ id: crypto.randomUUID(), ...post }, 201);
});
La valeur analysée est typée, ainsi c.req.valid("json") correspond exactement à la structure du schéma. Vous pouvez personnaliser la réponse en cas d’échec à l’aide d’un hook, ce qui permet de garder des corps d’erreur cohérents avec le reste de votre API.
RPC : des types de bout en bout sans codegen
Le mode RPC est la fonctionnalité phare de Hono. Si vous exportez le type de votre application, le client peut en inférer chaque route et chaque réponse directement.
// server.ts
const route = app
.get("/api/users", (c) => c.json([{ id: "1", name: "Ada" }]))
.post(
"/api/users",
zValidator("json", CreateUser),
(c) => c.json({ id: crypto.randomUUID(), ...c.req.valid("json") }, 201),
);
export type AppType = typeof route;
// client.ts
import { hc } from "hono/client";
import type { AppType } from "./server";
const client = hc<AppType>("https://api.example.com");
const res = await client.api.users.$post({
json: { name: "Ada", email: "[email protected]" },
});
if (res.ok) {
const user = await res.json(); // typed from the server handler
}
Il n’y a aucun fichier de schéma à synchroniser et aucun client généré. Modifiez une route sur le serveur et le client ne compilera plus tant qu’il n’aura pas été mis à jour, transformant ainsi le décalage de l’API (API drift) en erreur de build. Cela fonctionne idéalement dans un monorepo ou un package partagé où les deux parties peuvent importer le type.
Rendu HTML et JSX
Hono peut servir du HTML directement, soit sous forme de chaînes de caractères, soit via son propre runtime JSX. Le JSX est conçu pour le rendu serveur : pas de DOM virtuel, pas d’hydratation, juste une sortie sous forme de chaîne.
import { Hono } from "hono";
import { html } from "hono/html";
const app = new Hono();
app.get("/", (c) => {
return c.html(
html`<!doctype html>
<html>
<body><h1>Hello from ${c.req.query("name") ?? "the edge"}</h1></body>
</html>`,
);
});
Pour une expérience de templating plus complète, hono/jsx propose des composants et des layouts, tandis que des helpers comme html gèrent l’échappement. C’est une solution idéale pour les pages rendues côté serveur et pour les fragments HTML retournés par une API edge.
Un seul codebase, plusieurs runtimes
L’objectif est la portabilité. Le code de l’application n’importe jamais de module spécifique à un runtime ; seul le point d’entrée change.
// Node
import { serve } from "@hono/node-server";
import app from "./app";
serve({ fetch: app.fetch, port: 3000 });
// Cloudflare Workers
export default app;
// Bun
export default { port: 3000, fetch: app.fetch };
Le même objet app dessert les trois. Cela signifie qu’un service prototypé sur Node peut migrer vers Workers pour des raisons de coût ou de latence sans nécessiter de réécriture, et qu’une application Workers peut s’exécuter dans une suite de tests locale via l’adaptateur Node.
Déploiement à l’edge
Sur Cloudflare Workers, le déploiement se résume à une commande Wrangler et un petit fichier de configuration.
pnpm add -D wrangler
npx wrangler deploy
# wrangler.toml
name = "api"
main = "src/index.ts"
compatibility_date = "2026-09-01"
[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxxxxxxxxxx"
Les bindings définis ici apparaissent sur c.env, et sont entièrement typés si vous les passez comme générique Bindings à new Hono<{ Bindings: Bindings }>(). Vercel, Deno Deploy et Netlify disposent chacun d’un adaptateur documenté, et la même application se déploie généralement avec une modification d’une seule ligne dans l’entrée. Gardez à l’esprit les contraintes du runtime : limitez les tâches gourmandes en CPU, évitez les connexions aux bases de données de longue durée et utilisez du stockage natif pour l’edge.
Bonnes pratiques
- Typez les liaisons et les variables via les génériques
Honoafin quec.envetc.getsoient sécurisés. - Validez chaque entrée avec
zValidatoret conservez le schéma à côté de la route. - Composez vos middleware avec
createMiddlewareet délimitez leur portée avec un pattern de chemin. - Exportez
AppTypeet utilisezhccôté client au lieu de types écrits à la main. - Gardez vos handlers légers ; déplacez la logique réutilisable dans des fonctions simples ou des services.
- Retournez les codes de statut appropriés avec
c.json(body, status)plutôt que du 200 systématiquement. - Concevez pour le runtime : pas de système de fichiers, CPU limité, stockage edge-native.
- Testez avec
app.request()pour que vos tests n’aient besoin ni de serveur ni de réseau.
test("GET /posts", async () => {
const res = await app.request("/posts");
expect(res.status).toBe(200);
});
Erreurs courantes
- Supposer que les bibliothèques Node fonctionnent sur Workers ; les modules intégrés et les addons natifs ne sont souvent pas compatibles.
- Maintenir des connexions à la base de données ouvertes dans un runtime edge au lieu d’utiliser HTTP ou des bindings.
- Oublier de retourner la réponse, ce qui fait que le handler se résout en
undefined. - Enregistrer
corsaprès les routes auxquelles il devrait s’appliquer. - Lire
c.req.json()plus d’une fois, ce qui échoue car le flux du corps (body stream) est consommé. - Sauter l’étape de validation et faire confiance aux chaînes de caractères
c.req.query(). - Laisser
AppTypediverger en écrivant manuellement les types côté client au lieu de les importer. - Effectuer des tâches lourdes pour le CPU lors d’une requête et atteindre la limite de temps du runtime.
Et après ?
Hono apporte le modèle de routage et de middleware que vous connaissez déjà via Express vers des runtimes qui n’existaient pas lors de la création d’Express. Si vous avez besoin d’un serveur Node pérenne avec une validation basée sur des schémas, comparez-le avec Fastify. Pour comprendre les runtimes ciblés par Hono, consultez le guide sur les bases de Node.js, et lorsque vous serez prêt pour la mise en production, la roadmap backend couvre le déploiement sur le cloud. Ensuite, créez une petite API, déployez-la sur Workers et appelez-la avec un client RPC typé.