Skip to main content
← All Articles · · 14 min read

Hono vs Express: The New Node.js Server Framework Battle in 2026

A comprehensive comparison of Hono and Express in 2026 — performance benchmarks, middleware, TypeScript support, edge deployment, ecosystem maturity, and which framework to choose for your next API.

honoexpressnodejsframeworktypescriptedgeapibackendperformance

Express.js has been the default answer to “how do I build a Node.js API” for over a decade. It’s stable, battle-tested, and every Node.js developer knows it. But in 2026, Hono has emerged as a serious contender — not just a curiosity — offering dramatically better performance, TypeScript-first design, and native edge deployment.

This is a technical comparison for developers deciding which framework to use for new projects in 2026.


Quick Comparison: At a Glance

HonoExpress
First release20212010
LanguageTypeScript (first-class)JavaScript (TypeScript via types)
Throughput (Node.js)~230k req/sec~80k req/sec
Throughput (Cloudflare Workers)~1.2M req/sec❌ Not supported
Bundle size~14KB~500KB (with dependencies)
Middleware compatibilityLimited Express compatMassive ecosystem
Edge deployment✅ Native❌ Node.js only
WebSocket✅ Built-in⚠️ via ws package
Validation✅ Zod via @hono/zod-validatorManual
OpenAPI generation✅ @hono/zod-openapiManual
RPC client✅ hono/client❌
Runtime supportNode.js, Bun, Deno, CF Workers, Vercel, NetlifyNode.js, Bun
Stars (GitHub 2026)~21k~64k
Weekly downloads~3M~30M

Express: The Standard

Express has been the backbone of Node.js APIs since 2010. If you’ve written a Node.js backend, you’ve almost certainly used it.

const express = require("express");
const app = express();

app.use(express.json());

app.get("/users/:id", async (req, res) => {
  try {
    const user = await db.user.findUnique({ where: { id: req.params.id } });
    if (!user) return res.status(404).json({ error: "Not found" });
    res.json(user);
  } catch (err) {
    res.status(500).json({ error: "Internal server error" });
  }
});

app.listen(3000);

Express’s success comes from its middleware pattern — a simple, composable system where functions transform (req, res, next):

// Logging middleware
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

// Auth middleware
const requireAuth = (req, res, next) => {
  if (!req.headers.authorization) {
    return res.status(401).json({ error: "Unauthorized" });
  }
  next();
};

app.get("/protected", requireAuth, (req, res) => {
  res.json({ secret: "data" });
});

This pattern, while simple, has known downsides: no TypeScript inference for req.params, no built-in validation, and performance that hasn’t kept pace with modern alternatives.


Hono: The New Challenger

Hono (炎, Japanese for “flame”) was built for the edge computing era. It runs on Cloudflare Workers, Deno Deploy, Bun, Vercel Edge, and Node.js — the same code, everywhere.

import { Hono } from "hono";

const app = new Hono();

app.get("/users/:id", async (c) => {
  const id = c.req.param("id"); // TypeScript knows this is string
  const user = await db.user.findUnique({ where: { id } });

  if (!user) return c.json({ error: "Not found" }, 404);
  return c.json(user);
});

export default app;

The context object c gives you typed access to everything — params, query, body, headers, and the response builder.

TypeScript DX in Hono

This is where Hono genuinely outclasses Express:

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

const app = new Hono();

const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  role: z.enum(["admin", "user"]).default("user")
});

app.post(
  "/users",
  zValidator("json", createUserSchema), // validates body
  async (c) => {
    const body = c.req.valid("json"); // fully typed: { name: string, email: string, role: "admin" | "user" }
    const user = await db.user.create({ data: body });
    return c.json(user, 201);
  }
);

Compare this to Express where you’d need to manually type req.body and run validation separately with no automatic TypeScript inference.


Performance Benchmarks

HTTP Throughput (Node.js)

Simple "Hello World" endpoint:
Hono:    ~230,000 req/sec
Fastify: ~180,000 req/sec
Koa:     ~120,000 req/sec
Express: ~80,000 req/sec

Benchmarks: wrk, 4 threads, 100 connections, 30s, Node.js 22, Apple M3 Pro.

Hono is consistently 2-3× faster than Express for simple endpoints. The gap narrows for I/O-bound handlers (database queries dominate latency), but for high-throughput APIs, the difference is real.

Edge Performance (Cloudflare Workers)

Cloudflare Workers (global edge):
Hono:         ~1.2M req/sec, <1ms p50 latency
Itty Router:  ~900k req/sec
Express:      ❌ Doesn't run on CF Workers

This is where Hono has no competition. Edge deployment with near-zero cold starts is a category Express simply doesn’t play in.

Bundle Size

# Hono — zero dependencies, 14KB
npm pack hono  # 14.3KB

# Express — many dependencies
npm pack express  # 58KB source, ~500KB with node_modules

For edge functions where bundle size affects startup, Hono’s minimal footprint matters.


Routing

Hono Routing

import { Hono } from "hono";

const app = new Hono();

// Path params
app.get("/posts/:id", (c) => {
  const id = c.req.param("id");
  return c.json({ id });
});

// Wildcard
app.get("/static/*", serveStatic({ root: "./public" }));

// Method grouping
app.route("/api", new Hono()
  .get("/users", listUsers)
  .post("/users", createUser)
  .get("/users/:id", getUser)
  .put("/users/:id", updateUser)
  .delete("/users/:id", deleteUser)
);

// Nested routers
const apiRouter = new Hono();
const usersRouter = new Hono();
usersRouter.get("/", listUsers);
usersRouter.post("/", createUser);
apiRouter.route("/users", usersRouter);
app.route("/api", apiRouter);

Express Routing

const router = express.Router();

router.get("/", listUsers);
router.post("/", createUser);
router.get("/:id", getUser);

app.use("/api/users", router);

Both routing approaches are similar in concept. Hono’s is more composable and TypeScript-friendly.


Middleware

Hono Middleware

import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
import { bearerAuth } from "hono/bearer-auth";
import { rateLimiter } from "hono-rate-limiter";
import { cache } from "hono/cache";

const app = new Hono();

// Built-in middleware
app.use("*", logger());
app.use("*", cors({ origin: "https://myapp.com" }));
app.use("/api/*", bearerAuth({ token: process.env.API_TOKEN }));

// Caching (edge-compatible)
app.use("/static/*", cache({
  cacheName: "static-assets",
  cacheControl: "max-age=3600"
}));

Express Middleware

const express = require("express");
const cors = require("cors");
const morgan = require("morgan");
const rateLimit = require("express-rate-limit");

app.use(morgan("combined"));
app.use(cors({ origin: "https://myapp.com" }));
app.use(rateLimit({ windowMs: 60000, max: 100 }));

Express has a much larger middleware ecosystem — thousands of npm packages. Hono’s ecosystem is smaller but growing fast, and its built-in middleware covers the most common cases.


Edge Deployment

This is Hono’s most significant advantage in 2026.

Hono on Cloudflare Workers

// worker.ts — deploys to Cloudflare's 300+ global PoPs
import { Hono } from "hono";

const app = new Hono();

app.get("/api/data", async (c) => {
  // Access Cloudflare KV, D1, R2 directly
  const data = await c.env.MY_KV.get("key");
  return c.json({ data });
});

export default app;
# wrangler.toml
name = "my-api"
main = "src/worker.ts"
compatibility_date = "2026-01-01"

kv_namespaces = [{ binding = "MY_KV", id = "..." }]

Hono on Multiple Runtimes

The same Hono app runs everywhere:

// main.ts — runs on Node.js, Bun, Deno, or edge
const app = new Hono();
app.get("/", (c) => c.text("Hello!"));

// Node.js adapter
import { serve } from "@hono/node-server";
serve(app);

// Bun — direct
export default app;

// Deno
Deno.serve(app.fetch);

// Vercel Edge
export default app;

Express has no equivalent path to edge deployment.


RPC Client — Hono’s Killer Feature

Hono’s most distinctive feature is its typed RPC client. Define your API routes and get a fully typed client with zero code generation:

// server/routes.ts
import { Hono } from "hono";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

const routes = new Hono()
  .get("/users", async (c) => {
    const users = await db.user.findMany();
    return c.json({ users });
  })
  .post(
    "/users",
    zValidator("json", z.object({ name: z.string(), email: z.string().email() })),
    async (c) => {
      const body = c.req.valid("json");
      const user = await db.user.create({ data: body });
      return c.json(user, 201);
    }
  );

export type AppType = typeof routes;
// client/api.ts — fully typed, no codegen needed
import { hc } from "hono/client";
import type { AppType } from "../server/routes";

const client = hc<AppType>("http://localhost:3000");

// TypeScript knows the return type
const { data } = await client.users.$get();
// data.users is typed from the server response

// POST body is validated at compile time
await client.users.$post({
  json: { name: "Alice", email: "[email protected]" }
});

This is similar to tRPC but lighter and built into the framework. It’s a compelling DX for full-stack TypeScript projects.


OpenAPI Integration

import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";

const app = new OpenAPIHono();

const getUserRoute = createRoute({
  method: "get",
  path: "/users/{id}",
  request: {
    params: z.object({ id: z.string() })
  },
  responses: {
    200: {
      content: { "application/json": { schema: z.object({ name: z.string() }) } },
      description: "Get user by ID"
    }
  }
});

app.openapi(getUserRoute, async (c) => {
  const { id } = c.req.valid("param");
  const user = await db.user.findUnique({ where: { id } });
  return c.json(user!);
});

// Auto-generates OpenAPI spec
app.doc("/openapi.json", {
  openapi: "3.0.0",
  info: { title: "My API", version: "1.0.0" }
});

Express has no equivalent without significant boilerplate or third-party tools.


When to Choose Which

Choose Hono When:

  • Edge deployment is a requirement (Cloudflare Workers, Vercel Edge)
  • TypeScript-first development and you want end-to-end types
  • Performance matters — high throughput, minimal overhead
  • Multi-runtime — same code needs to run on Node.js, Bun, and Deno
  • Full-stack TypeScript — want the typed RPC client
  • Starting a new project in 2026

Choose Express When:

  • Massive ecosystem of middleware matters (express-session, passport.js, etc.)
  • Existing codebase on Express — don’t migrate without clear benefit
  • Team familiarity — large team that knows Express deeply
  • Legacy constraints — must support very old Node.js versions
  • You need a specific middleware that only exists for Express

Migration Path: Express → Hono

The core patterns translate closely:

// Express
app.use(express.json());
app.get("/users/:id", async (req, res) => {
  const user = await getUser(req.params.id);
  res.json(user);
});
// Hono equivalent
app.get("/users/:id", async (c) => {
  const user = await getUser(c.req.param("id"));
  return c.json(user);
});

The main friction points when migrating:

  • Express middleware won’t work with Hono (different middleware signature)
  • req/res becomes c (context object)
  • Response is returned, not called (return c.json() not res.json())
  • passport.js and similar auth libraries need replacement (use hono/bearer-auth or custom)

The Verdict

In 2026, Hono is the better choice for new projects. The performance advantage, TypeScript-first design, edge deployment support, and RPC client make it the more capable framework for modern use cases.

Express remains relevant for projects already using it and teams that rely heavily on its middleware ecosystem. The ecosystem advantage is real — thousands of battle-tested packages versus Hono’s growing but smaller catalog.

For anything starting fresh today: Hono. For existing Express apps: evaluate the migration cost against the benefits based on your specific needs.

Free Download

50 CLI Commands That Save Hours Every Week

The exact Git shortcuts, file search tricks, Docker commands, and productivity hacks that senior developers use daily. Compiled from 5+ years of shipping production code.

  • Git mastery (10 cmds)
  • File & search wizardry
  • Docker & DevOps tricks
  • Process & network tools
  • Productivity boosters
  • macOS & Linux install guide

No spam. 1 email/week max. Unsubscribe anytime.

Found this guide useful? Check out our free developer tools.