O que é Hono?
Hono é um framework web pequeno e rápido construído sobre a Web Platform. Em vez de inventar seus próprios objetos de requisição e resposta, ele utiliza as classes padrão Request e Response que navegadores, Cloudflare Workers, Deno e Bun fornecem. Sobre essa base, ele adiciona roteamento, middleware e um objeto de contexto, e nada mais do que você não solicite.
O nome significa “chama” em japonês, e o projeto foca totalmente em velocidade: um núcleo minúsculo, sem dependências integradas do Node e um roteador projetado para cold starts. Essa combinação tornou o Hono a escolha padrão para APIs de edge, onde cada milissegundo de inicialização impacta cada requisição. Como o contrato do runtime é o padrão web, a mesma aplicação também roda em Node, portanto, você nunca fica preso a um único ecossistema.
Roteamento baseado em padrões web
O roteamento parece deliberadamente familiar. Um método e um caminho mapeiam para um handler, parâmetros utilizam :name e wildcards utilizam *.
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));
Os routers podem ser divididos em sub-apps e montados, que é a forma como serviços maiores se mantêm organizados:
import { Hono } from "hono";
const api = new Hono();
api.get("/users", listUsers);
api.get("/users/:id", getUser);
app.route("/api", api);
Todo handler retorna um Response. Os helpers de contexto — c.json, c.text, c.html, c.redirect, c.body — constroem a resposta correta com headers e status, e você sempre pode retornar um new Response(...) puro quando precisar de controle total.
O objeto context
O único argumento do handler é o context, convencionalmente nomeado como c. Ele contém a requisição, os helpers de resposta e um local para armazenar valores para a requisição atual.
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 expõe os bindings de runtime: variáveis de ambiente, namespaces KV, bancos de dados D1 e buckets R2 no Workers. c.set e c.get compartilham valores entre middleware e handlers, e c.var fornece acesso tipado a eles. Tudo o que você precisa para uma requisição reside em um único objeto, o que torna a composição de middleware simples e direta.
Middleware
Middleware é uma função assíncrona que recebe o contexto e next. Chame await next() para continuar a cadeia, ou retorne antecipadamente para interrompê-la (short-circuit).
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);
O helper createMiddleware adiciona inferência de tipos, e app.use aceita um padrão de caminho para que o middleware seja executado apenas onde for necessário. A ordem de registro é a ordem de execução, exatamente como no Express.
O Hono traz um conjunto útil de middleware no core:
corspara headers de cross-origin.loggerpara log de requisições.bearerAuthebasicAuthpara autenticação.cachepara cache de resposta em edge.etag,compressesecureHeaderspara higiene de HTTP.csrfpara proteção contra cross-site request forgery.
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 }));
Como estes são middlewares simples, eles se compõem com os seus próprios e com qualquer outra coisa no ecossistema.
Validação e respostas tipadas
O Hono possui validadores nativos para Zod e Valibot. Eles analisam um alvo (json, query, param, form), rejeitam entradas inválidas com um 400 e fornecem ao handler um valor tipado.
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);
});
O valor analisado é tipado, portanto c.req.valid("json") possui exatamente o formato do schema. Você pode customizar a resposta de falha com um hook, que é a maneira de manter os corpos de erro consistentes com o restante da sua API.
RPC: tipos end-to-end sem codegen
O modo RPC é o recurso de maior destaque do Hono. Se você exportar o tipo do seu app, o cliente consegue inferir cada rota e resposta diretamente dele.
// 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
}
Não há arquivo de schema para manter sincronizado nem cliente gerado. Altere uma rota no servidor e o cliente falhará na compilação até que seja atualizado, transformando o “API drift” em um erro de build. Isso funciona melhor em um monorepo ou em um pacote compartilhado onde ambos os lados podem importar o tipo.
Renderizando HTML e JSX
O Hono pode servir HTML diretamente, seja como strings ou através de seu próprio runtime JSX. O JSX foi projetado para renderização no servidor: sem virtual DOM, sem hidratação, apenas saída de string.
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>`,
);
});
Para uma experiência de templating mais completa, hono/jsx fornece componentes e layouts, e helpers como html lidam com o escape de caracteres. É uma ótima escolha para páginas renderizadas no servidor e para fragmentos de HTML retornados por uma edge API.
Um único codebase, múltiplos runtimes
A portabilidade é o objetivo. O código da aplicação nunca importa um módulo específico de runtime; apenas o ponto de entrada (entry point) muda.
// 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 };
O mesmo objeto app atende aos três. Isso significa que um serviço prototipado no Node pode migrar para Workers por questões de custo ou latência sem a necessidade de reescrita, e um app de Workers pode rodar em uma suíte de testes local através do adaptador de Node.
Fazendo o deploy na edge
No Cloudflare Workers, o deploy é feito através de um comando do Wrangler e um pequeno arquivo de configuração.
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"
Os Bindings definidos aqui aparecem no c.env, totalmente tipados se você os passar como o generic Bindings para new Hono<{ Bindings: Bindings }>(). Vercel, Deno Deploy e Netlify possuem adaptadores documentados, e o mesmo app geralmente é implantado com a alteração de apenas uma linha de entrada. Lembre-se das restrições de runtime: limite tarefas que exijam muito da CPU, evite conexões de banco de dados de longa duração e utilize armazenamento nativo de edge.
Melhores práticas
- Tipagem de bindings e variáveis através de generics
Honopara quec.envec.getsejam seguros. - Valide cada entrada com
zValidatore mantenha o schema próximo à rota. - Componha middleware com
createMiddlewaree defina o escopo com um padrão de caminho (path pattern). - Exporte
AppTypee utilizehcno cliente em vez de tipos escritos manualmente. - Mantenha os handlers enxutos; mova a lógica reutilizável para funções simples ou serviços.
- Retorne os códigos de status corretos com
c.json(body, status)em vez de sempre retornar 200. - Projete para o runtime: sem sistema de arquivos, CPU limitada e armazenamento edge-native.
- Teste com
app.request()para que os testes não precisem de servidor ou rede.
test("GET /posts", async () => {
const res = await app.request("/posts");
expect(res.status).toBe(200);
});
Erros comuns
- Assumir que bibliotecas de Node funcionam em Workers; built-ins e addons nativos frequentemente não funcionam.
- Manter conexões de banco de dados abertas em um edge runtime em vez de usar HTTP ou bindings.
- Esquecer de retornar a resposta, fazendo com que o handler resolva para
undefined. - Registrar
corsapós as rotas às quais ele deveria ser aplicado. - Ler
c.req.json()mais de uma vez, o que falha porque o stream do corpo é consumido. - Pular a validação e confiar em strings de
c.req.query(). - Deixar
AppTypedivergir ao escrever tipos de cliente manualmente em vez de importá-lo. - Executar tarefas pesadas de CPU em uma requisição e atingir o limite de tempo do runtime.
Próximos passos
O Hono traz o modelo de roteamento e middleware que você já conhece do Express para runtimes que sequer existiam quando o Express foi escrito. Se você precisa de um servidor Node de longa duração com validação baseada em schema, compare-o com o Fastify. Para entender os runtimes que o Hono suporta, leia o guia de fundamentos do Node.js e, quando estiver pronto para colocar em produção, o roadmap de backend cobre a implantação na nuvem. Depois, construa uma API pequena, faça o deploy no Workers e consuma-a com um cliente RPC tipado.