Skip to main content

Express vs Hono in 2026: Legacy vs Modern Node.js

·PkgPulse Team

TL;DR

Express has 20x more downloads but Hono is the better choice for new projects. Express (35M+ weekly downloads) won through ubiquity and age. Hono (1.8M) wins on TypeScript, performance, and modern runtime support. For greenfield APIs in 2026, Hono is the correct default. For maintaining Express apps, stay — migration isn't worth it for stable codebases.

Key Takeaways

  • Express: ~35M weekly downloads — Hono: ~1.8M (npm, March 2026)
  • Hono is 4-5x faster on Node.js; much faster on Bun
  • Express has no TypeScript — Hono is TypeScript-first with request type inference
  • Hono runs on Cloudflare Workers — Express cannot (Node.js APIs only)
  • Hono is Express-compatible — familiar API, easy learning curve

The Gap in Context

Express's download dominance is a trailing indicator. It powers millions of production Node.js applications — most of which aren't being rewritten. The install numbers reflect maintenance of existing systems, not new project adoption.

New project starts tell a different story. In 2026, Hono, Fastify, and Elysia have captured the majority of net-new backend Node.js applications. Express is still started for quick prototypes by developers who know it, but the ecosystem is clearly shifting.


API Design Comparison

// Express — callback-based, no types
const express = require('express');
const app = express();
app.use(express.json());

app.get('/users/:id', async (req, res) => {
  // req.params.id is string | undefined — no type safety
  const user = await db.user.findUnique({
    where: { id: req.params.id }
  });

  if (!user) return res.status(404).json({ error: 'Not found' });
  res.json(user);
});

app.post('/users', async (req, res) => {
  // req.body is any — needs manual validation
  const { name, email } = req.body;
  if (!name || !email) return res.status(400).json({ error: 'Missing fields' });

  const user = await db.user.create({ data: { name, email } });
  res.status(201).json(user);
});

app.listen(3000);
// Hono — TypeScript-first, web standards
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';

const app = new Hono();

app.get('/users/:id', async (c) => {
  const id = c.req.param('id'); // typed as string
  const user = await db.user.findUnique({ where: { id } });

  if (!user) return c.json({ error: 'Not found' }, 404);
  return c.json(user); // response type inferred
});

app.post('/users',
  zValidator('json', z.object({
    name: z.string().min(1),
    email: z.string().email(),
  })),
  async (c) => {
    const body = c.req.valid('json'); // { name: string, email: string }
    const user = await db.user.create({ data: body });
    return c.json(user, 201);
  }
);

export default app; // Works on Cloudflare Workers

The type safety difference is meaningful in large codebases. req.body being any in Express leads to undetected type errors. Hono's c.req.valid('json') returns the validated, typed body.


Middleware Comparison

// Express middleware (familiar but untyped)
app.use((req, res, next) => {
  req.user = verifyJWT(req.headers.authorization);
  next();
});

// Accessing custom properties requires declaration merging in TS
// This is a common pain point
// Hono middleware with typed context
import { createMiddleware } from 'hono/factory';

const authMiddleware = createMiddleware<{
  Variables: { user: { id: string; email: string } }
}>(async (c, next) => {
  const token = c.req.header('Authorization')?.slice(7);
  if (!token) return c.json({ error: 'Unauthorized' }, 401);

  const user = verifyJWT(token);
  c.set('user', user); // typed
  await next();
});

// Usage
app.use('/api/*', authMiddleware);
app.get('/api/me', (c) => {
  const user = c.get('user'); // { id: string; email: string }
  return c.json(user);
});

Hono's middleware typing is a significant DX improvement — you get full autocomplete on c.get('user') without declaration merging hacks.


Performance Benchmarks (Node.js)

FrameworkReq/sLatency (p99)
Hono~350K~2ms
Fastify~230K~3ms
Express~80K~8ms

Hono is ~4x faster than Express on equivalent hardware. For most applications, this doesn't matter — database queries dominate response times. But for high-throughput APIs (auth services, rate limiting, proxy layers), it's significant.


The @hono/node-server Migration Path

For Express developers, Hono's API is intentionally familiar:

// Express patterns that map directly to Hono
// app.get → app.get ✅
// app.post, app.put, app.delete → same ✅
// app.use(middleware) → app.use(middleware) ✅
// res.json() → c.json() ✅
// req.params → c.req.param() ✅
// req.query → c.req.query() ✅
// req.body → c.req.json() (async) ✅

// Deploy on Node.js
import { serve } from '@hono/node-server';
serve(app, (info) => {
  console.log(`Listening on port ${info.port}`);
});

When to Choose

Choose Express when:

  • Maintaining an existing Express codebase (don't rewrite stable code)
  • You need a very specific Express middleware with no Hono equivalent
  • Your team only knows Express and learning overhead is a constraint
  • You're building a quick proof-of-concept where familiarity wins

Choose Hono when:

  • Starting any new Node.js backend project in 2026
  • Deploying to Cloudflare Workers or other edge runtimes
  • TypeScript type safety matters to your team
  • Performance headroom is valuable
  • You want built-in Zod validation without boilerplate

Compare Express and Hono package health on PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.