APIs
|stacknotice.com
14 min left|
0%
|2,800 words
APIs

Hono vs Express vs Fastify (2026): Which Node.js Framework Is Actually Faster?

Express is familiar. Fastify is fast. Hono runs everywhere. Real benchmarks, code examples, and a clear decision framework for choosing your Node.js backend framework.

C
Carlos Oliva
Software Developer
August 7, 202614 min read
Share:
Hono vs Express vs Fastify (2026): Which Node.js Framework Is Actually Faster?

Choosing a Node.js backend framework in 2026 means choosing between three very different tools with different core priorities. Express optimizes for familiarity — the largest ecosystem, the most Stack Overflow answers, the framework your team already knows. Fastify optimizes for Node.js performance — JSON serialization, schema validation, and throughput at scale. Hono optimizes for portability — web standards, edge runtimes, and running the same code on Node.js, Bun, Cloudflare Workers, and Deno.

This isn't a complete guide to any of them. It's the comparison you need to make the right choice.

Performance: The Numbers That Actually Matter

Before the benchmarks, the context: framework performance matters at scale, and at modest traffic levels the difference between these three is often negligible compared to your database query time. That said, the numbers are real and worth understanding.

Requests per second (approximate, Node.js 22, JSON response)

Framework~ThroughputLatency (median)
Hono (Node.js)~85,000 req/s~0.5ms
Fastify~75,000 req/s~0.6ms
Express~30,000 req/s~1.4ms
Hono (Bun)~130,000+ req/s~0.3ms

Express is roughly 2-3x slower than Fastify and Hono on Node.js. The gap is real — Express's middleware model has overhead that accumulates. For high-traffic APIs where you're hitting 10k+ req/s, this difference is meaningful. For most applications processing hundreds or low thousands of requests per second, it isn't.

Hono on Bun stands apart — the Bun runtime itself is faster than Node.js for HTTP, and Hono's design aligns well with Bun's architecture.

Express — The Incumbent

Express has been the default Node.js web framework for over a decade. Its strength is accumulated ecosystem, not raw performance.

import express from 'express'
import { z } from 'zod'
 
const app = express()
app.use(express.json())
 
const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email()
})
 
app.post('/users', async (req, res) => {
  const parsed = createUserSchema.safeParse(req.body)
  if (!parsed.success) {
    return res.status(400).json({ errors: parsed.error.flatten() })
  }
 
  const user = await db.user.create({ data: parsed.data })
  res.status(201).json(user)
})
 
app.listen(3000)

Express has no built-in TypeScript support. No built-in schema validation. No built-in serialization. You bring your own for everything — which is also why there's an Express middleware for virtually every use case imaginable.

When Express still makes sense

  • Legacy codebases you're not rebuilding
  • Teams that know Express and the migration cost exceeds the performance benefit
  • Niche middleware you depend on that doesn't have a Fastify or Hono equivalent
  • Rapid prototyping where ecosystem familiarity > everything else

Express is not the wrong choice. It's the choice that requires the most defensive coding: manual validation, manual error handling, manual TypeScript types for req and res. If you're starting fresh in 2026, both Fastify and Hono offer a better baseline.

Fastify — The Performance Pick for Node.js

Fastify is what you get when you rebuild Express with performance and TypeScript as first-class concerns. The JSON serialization is compiled from schema (2-3x faster than JSON.stringify). Schema validation via JSON Schema (or TypeBox) runs before your handler. The TypeScript experience is native.

import Fastify from 'fastify'
import { Type } from '@sinclair/typebox'
 
const fastify = Fastify({ logger: true })
 
const CreateUserBody = Type.Object({
  name: Type.String({ minLength: 1 }),
  email: Type.String({ format: 'email' })
})
 
fastify.post('/users', {
  schema: {
    body: CreateUserBody,
    response: {
      201: Type.Object({
        id: Type.String(),
        name: Type.String(),
        email: Type.String()
      })
    }
  }
}, async (request, reply) => {
  // request.body is fully typed from the schema
  const user = await db.user.create({ data: request.body })
  reply.status(201).send(user)
})
 
await fastify.listen({ port: 3000 })

The schema does two things: validates incoming data and compiles the response serializer. The response type annotation isn't just TypeScript — Fastify uses it to build a fast serializer at startup. You get both type safety and runtime performance from the same definition.

Plugin system

Fastify's plugin system uses fastify-plugin for encapsulation. Each plugin gets its own context and can't accidentally pollute others:

import fp from 'fastify-plugin'
 
// Registers a plugin available to all routes (fp = not encapsulated)
export const authPlugin = fp(async (fastify) => {
  fastify.decorate('authenticate', async (request, reply) => {
    const token = request.headers.authorization?.replace('Bearer ', '')
    if (!token) return reply.status(401).send({ error: 'Unauthorized' })
 
    const payload = verifyJWT(token)
    request.user = payload
  })
})
 
// Route-level plugin (encapsulated — only visible in this scope)
async function adminRoutes(fastify) {
  fastify.addHook('onRequest', fastify.authenticate)
 
  fastify.get('/admin/stats', async () => {
    return await db.getSystemStats()
  })
}

When Fastify is the right call

  • High-throughput APIs running on Node.js where performance matters
  • Teams that want schema-first development with TypeBox or JSON Schema
  • Applications staying on Node.js (no edge/multi-runtime requirement)
  • When you want Express-like familiarity with a significant performance upgrade

Hono — The Edge-First Framework

Hono is built on web standards — Request, Response, Headers — which means it runs anywhere those standards are implemented: Node.js, Bun, Cloudflare Workers, Deno, AWS Lambda, Vercel Edge Functions.

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()
})
 
app.post(
  '/users',
  zValidator('json', createUserSchema),
  async (c) => {
    const body = c.req.valid('json')  // typed from schema
    const user = await db.user.create({ data: body })
    return c.json(user, 201)
  }
)
 
export default app  // same export works on Node, Bun, Cloudflare Workers

The same app object runs on Node.js with @hono/node-server, on Bun directly, or deployed to Cloudflare Workers as-is. No adapter needed, no code changes — the runtime is a deployment decision, not a code decision.

Hono's middleware ecosystem

Hono ships with built-in middleware that covers most API needs:

import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { compress } from 'hono/compress'
import { rateLimiter } from 'hono-rate-limiter'
import { jwt } from 'hono/jwt'
 
const app = new Hono()
 
app.use('*', logger())
app.use('*', cors({ origin: 'https://myapp.com' }))
app.use('*', compress())
 
// JWT auth on specific routes
app.use('/api/*', jwt({ secret: process.env.JWT_SECRET! }))
 
// Rate limiting
app.use('/api/*', rateLimiter({
  windowMs: 60_000,
  limit: 100,
  keyGenerator: (c) => c.req.header('x-forwarded-for') ?? 'unknown'
}))

RPC with type safety across client and server

Hono has a first-class RPC mode that gives you end-to-end type safety between your API and any TypeScript client — similar to tRPC but without requiring a separate setup:

// server.ts
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
 
const routes = new Hono()
  .get('/users/:id', async (c) => {
    const user = await db.user.findUnique({ where: { id: c.req.param('id') } })
    return c.json(user)
  })
  .post(
    '/users',
    zValidator('json', createUserSchema),
    async (c) => {
      const user = await db.user.create({ data: c.req.valid('json') })
      return c.json(user, 201)
    }
  )
 
export type AppType = typeof routes
// client.ts
import { hc } from 'hono/client'
import type { AppType } from './server'
 
const client = hc<AppType>('http://localhost:3000')
 
// Fully typed — no code generation required
const user = await client.users[':id'].$get({ param: { id: '123' } })
const data = await user.json()  // type: User

When Hono is the right call

  • Multi-runtime — you want to deploy to Cloudflare Workers, Bun, or Deno with zero code changes
  • Edge deployments — APIs running at the edge where response time matters at the P99 level
  • New projects starting fresh where portability is a future option
  • Bun — Hono + Bun is the fastest HTTP combination in the Node.js ecosystem
  • Teams that want tRPC-like RPC without the tRPC dependency

The Same Endpoint in All Three

Real comparison: a POST endpoint that validates input, queries a database, and returns a typed response.

// Express
app.post('/posts', async (req, res) => {
  const parsed = createPostSchema.safeParse(req.body)
  if (!parsed.success) return res.status(400).json({ error: parsed.error })
  const post = await db.post.create({ data: parsed.data })
  res.status(201).json(post)
})
 
// Fastify
fastify.post('/posts', {
  schema: { body: CreatePostBody, response: { 201: PostResponse } }
}, async (request, reply) => {
  const post = await db.post.create({ data: request.body })
  reply.status(201).send(post)
})
 
// Hono
app.post('/posts', zValidator('json', createPostSchema), async (c) => {
  const post = await db.post.create({ data: c.req.valid('json') })
  return c.json(post, 201)
})

Fastify wins on compiled performance (schema-based serialization). Hono wins on portability and conciseness. Express requires the most boilerplate for equivalent type safety.

Decision Framework

Choose Express if:

  • The codebase is already Express
  • The team knows Express and migration cost > benefit
  • You need a specific Express middleware with no equivalent elsewhere

Choose Fastify if:

  • You're staying on Node.js
  • Schema-first development with TypeBox fits your style
  • You need the absolute best throughput on Node.js
  • You're migrating from Express and want a familiar mental model

Choose Hono if:

  • You might deploy to Cloudflare Workers, Bun, or Deno now or in the future
  • You want RPC-style type safety without tRPC's setup
  • You're using Bun and want the fastest possible combination
  • You're building an API that should run in multiple environments

For deep dives on each individually, see the Hono complete guide and Fastify complete guide. For the database layer that pairs well with any of these, see the Drizzle ORM guide — it works equally well across all three frameworks.

#nodejs#hono#express#fastify#typescript#backend
Share:
C
Carlos Oliva
Software Developer · stacknotice.com

Software developer with hands-on experience building production apps with React, Next.js, Angular, TypeScript, and Spring Boot. I write practical guides on Claude Code, AI tools, and modern web development — covering the decisions and trade-offs that senior-level tutorials actually explain.

More about Carlos

Enjoyed this article?

Get weekly insights on Claude Code, React, and AI tools — practical guides for developers who build real things.

No spam. Unsubscribe anytime. By subscribing you agree to our Privacy Policy.