mrkeyoor.com_
Sat 19 Sept 17:58 UTC
npmWeb Backendupdated 19 Sept 2026

hono review

Hono 4 is an HTTP framework built around Web `Request`, `Response`, `Headers`, and `fetch` instead of Node's native request and response classes. One route tree can target Cloudflare Workers, Bun, Deno, Lambda, Node, and other adapters, while TypeScript route definitions can feed Hono's typed client. Version 4.13.5 fixes query parsing after URL fragments, an SSG path traversal gap, and memory exhaustion through deeply nested dot-notation form fields. Core Hono does not choose your database, job runner, or application structure, and Node needs a separate server adapter.

58.9Mdownloads / wk
Verdict

Hono 4.13.3 installed as 1 package in 0.8 seconds and its full import bundled to 7.6 KB gzipped in our sandbox, with 0 audit findings on 2026-08-22. Choose current 4.13.5 for typed Web API handlers across runtimes, but keep Express or Fastify when Node-specific middleware is the harder dependency to replace.

We installed it

Lab card: what happened when we installed honoScreenshot of hono documentation
Install✓ · 0.8s1 package on disk · 4 MB
ImportESM import works · require() works · ESM package with exports map
Browser7.6 KBgzipped (18.3 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does hono install cleanly?

Yes. In a fresh container with an empty cache, npm install hono finished in 0.8s, leaving 1 package and 4 MB on disk. npm audit reported no known vulnerabilities.

How much does hono add to a browser bundle?

7.6 KB gzipped (18.3 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does hono work with both ESM and CommonJS?

Yes. Both import 'hono' and require('hono') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does hono include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

hono or fastify: which should you use?

fastify: Use it for a Node-first API that benefits from compiled schemas and a mature plugin contract. Hono 4.13.3 installed as 1 package in 0.8 seconds and its full import bundled to 7.6 KB gzipped in our sandbox, with 0 audit findings on 2026-08-22.

When should you not use hono?

The application already depends on Express middleware that mutates req or res. Those objects and conventions do not map directly to Hono's Web API context.

API stability4/5Hono 4 keeps the same `new Hono()`, context, route handler, middleware, `app.fetch`, and `app.request` shape for ordinary applications. Patch releases still touch sensitive edges: 4.13.5 changes query parsing, static generation containment, and dot-notation body parsing. Pin the package and test wildcard routes, middleware ordering, generated client types, and helpers that sit outside the most common request path.
Docs4/5hono.dev has separate guides for runtimes, routing, validation, middleware, RPC clients, testing, JSX, streaming, and helper modules. Examples usually identify the adapter or extra package they require. Production behavior can span three manuals, though: Hono's page, its adapter page, and the hosting platform's proxy or binding documentation. That split is most noticeable for streaming, forwarded addresses, and cache control.
Maintenance5/5Version 4.13.5 was published on 2026-08-26 with three security fixes and direct upgrade guidance. GitHub shows a push the same day, 371 open issues and pull requests in its combined counter, and an unarchived repository. Frequent releases are backed by linked pull requests and comparisons, although a framework spanning many runtimes naturally accumulates adapter and edge-case work.
Ecosystem4/5npm counted 58,114,080 downloads from 2026-08-19 through 2026-08-25, and GitHub reports 31,972 stars. Official exports cover CORS, cookies, JWT, CSRF, caching, streaming, static files, testing, and several runtime adapters. The ecosystem is substantial, yet Express still has more vendor middleware, and external Hono packages do not all receive the same cross-runtime testing as core.

Discussed on

  1. hnHonoJS: Small, simple, and ultrafast web framework for the Edges300 points
  2. hnHono v4.0181 points
  3. hnHono: Ultrafast Web Framework for Cloudflare Workers, Deno, and Bun4 points
  4. hnHono: Fast, Lightweight, Web-Standards3 points

Use it if

  • HTTP handlers may move between a Worker, Node, Bun, Deno, or a serverless adapter and should keep Web API types.
  • A TypeScript backend should expose route input and output types to a generated client without a second handwritten interface.
  • Cold-started deployments benefit from a core package with no direct or peer dependencies.
  • The team wants routing and middleware without adopting a framework-wide ORM, dependency container, or module system.
Skip it if

Setup reality

We installed hono 4.13.3 in 0.8 seconds in a fresh Node 22 container. That run left 1 package and 4 MB on disk. The package had 0 direct dependencies, 0 peers, 3,664 KB unpacked, bundled TypeScript declarations, and an MIT license. npm audit reported 0 known vulnerabilities on 2026-08-22. ESM import and CommonJS require() both worked. Our full-package browser build measured 18.3 KB minified and 7.6 KB gzipped.

The registry now serves 4.13.5, so those install figures belong to 4.13.3 rather than today's tarball. Upgrade matters: 4.13.5 stops query parsing at URL fragments, closes a remaining toSSG() output-directory escape, and bounds dot-notation nesting in parseBody(). The package declares Node 16.9.0 or newer. Core Hono does not listen on a Node port; install @hono/node-server and pass app.fetch to serve().

No credentials or mandatory config file come with Hono. Runtime bindings do vary. A Worker commonly exposes secrets through c.env, while a Node service may read process.env. Put that boundary in an adapter layer if route code must move. Parsing is separate from validation: add middleware such as @hono/zod-validator before reading c.req.valid('json'). Middleware registration order decides which routes it wraps.

Hosts still control streaming and network metadata. A proxy can buffer streamSSE() output, and a CDN can replace cache headers on a Web Response. Forwarded addresses are trustworthy only when the deployment owns the proxy path. Code written around res.write(), Node streams, or Express response mutation needs translation to Web Streams and returned Response objects. Test mounted wildcards, preflight responses, caching, and streamed output on the actual adapter, since those are where recent patch releases have concentrated fixes.

Patterns

Return text and JSON define-routes

import { Hono } from 'hono';

const app = new Hono();
app.get('/', (c) => c.text('hello'));
app.get('/health', (c) => c.json({ ok: true }));

export default app;

This export works on fetch-style runtimes; Node still needs an adapter that opens a listening socket.

Listen with the Node adapter serve-on-node

import { serve } from '@hono/node-server';
import app from './app.js';

serve({ fetch: app.fetch, port: 3000 });

Install `@hono/node-server` separately because the `hono` package does not start a Node server.

Read path and query values read-request-input

app.get('/users/:id', (c) => {
  const id = c.req.param('id');
  const page = Number(c.req.query('page') ?? '1');
  return c.json({ id, page });
});

Query values are strings or undefined; `Number()` can produce `NaN` and does not enforce an allowed range.

Validate a JSON body with Zod validate-json

import { z } from 'zod';
import { zValidator } from '@hono/zod-validator';

const input = z.object({ name: z.string().min(1) });
app.post('/users', zValidator('json', input), (c) => {
  const body = c.req.valid('json');
  return c.json(body, 201);
});

Install Zod and the validator package; `c.req.json()` by itself parses data without checking its shape.

Measure handler duration write-middleware

app.use(async (c, next) => {
  const started = performance.now();
  await next();
  c.header('Server-Timing', `app;dur=${performance.now() - started}`);
});

Register middleware before its target routes; statements after `await next()` run as the response unwinds.

Apply CORS to an API prefix configure-cors

import { cors } from 'hono/cors';

app.use('/api/*', cors({
  origin: 'https://app.example.com',
  allowMethods: ['GET', 'POST'],
}));

Test preflight through the deployed CDN because caches must respect `Vary: Origin` for shared responses.

Return typed HTTP failures handle-errors

import { HTTPException } from 'hono/http-exception';

app.get('/private', () => {
  throw new HTTPException(403, { message: 'forbidden' });
});

app.onError((error, c) => {
  if (error instanceof HTTPException) return error.getResponse();
  console.error(error);
  return c.json({ error: 'internal_error' }, 500);
});

`HTTPException` carries its status and headers; log unexpected exceptions before returning a generic response.

Infer a client from chained routes generate-typed-client

const routes = app
  .get('/hello', (c) => c.json({ message: 'hi' }))
  .post('/hello', (c) => c.json({ saved: true }));
export type AppType = typeof routes;

import { hc } from 'hono/client';
const client = hc<AppType>('https://api.example.com');
const response = await client.hello.$get();

Keep route calls chained when exporting the app type, or TypeScript may lose information needed by `hc`.

Write a server-sent event stream-sse

import { streamSSE } from 'hono/streaming';

app.get('/events', (c) => streamSSE(c, async (stream) => {
  await stream.writeSSE({
    event: 'ready',
    data: JSON.stringify({ ok: true }),
  });
}));

A reverse proxy may buffer this response; verify that one event arrives before the connection closes.

Mount a route group mount-subapp

const users = new Hono();
users.get('/', (c) => c.json([]));
users.get('/:id', (c) => c.json({ id: c.req.param('id') }));

app.route('/users', users);

Exercise wildcard and suffix cases around mounted prefixes after upgrades because router fixes often land in patch releases.

Type an environment binding type-bindings

type Bindings = { API_TOKEN: string };
const app = new Hono<{ Bindings: Bindings }>();

app.get('/token-check', (c) => {
  return c.json({ configured: Boolean(c.env.API_TOKEN) });
});

The runtime adapter populates `c.env`; a Node deployment must provide its own mapping from process environment values.

Call a route without a port test-route

import { expect, it } from 'vitest';
import app from './app';

it('returns health', async () => {
  const response = await app.request('/health');
  expect(response.status).toBe(200);
  expect(await response.json()).toEqual({ ok: true });
});

`app.request()` stays inside the process; adapter, proxy, and socket behavior require a deployed integration test.

Alternatives

PackageRegistryPick it when
fastifynpmUse it for a Node-first API that benefits from compiled schemas and a mature plugin contract.
expressnpmUse it when an existing Node codebase depends on Express request objects and its broad middleware catalog.
elysianpmUse it for a Bun-centered service that prefers Elysia's integrated schema and plugin model.

More web backend guides

urllib3 · requests · ws · anyio · undici · httpx · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.