DEV Community

Carlos Oliva Pascual
Carlos Oliva Pascual

Posted on • Originally published at stacknotice.com

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 tools with very different priorities. Express optimizes for familiarity. Fastify optimizes for Node.js performance. Hono optimizes for portability across runtimes.

Performance: The Numbers That Matter

Framework Throughput Latency
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 ~2-3x slower than Fastify and Hono. The gap is real but only meaningful at scale (10k+ req/s). For most apps, database query time dominates.

Express — The Incumbent

import express from 'express'
import { z } from 'zod'

const app = express()
app.use(express.json())

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)
})
Enter fullscreen mode Exit fullscreen mode

No built-in TypeScript. No built-in validation. No built-in serialization. You bring everything. That's also why there's an Express middleware for virtually every use case.

Choose Express when: legacy codebase, team already knows it, or you need a specific middleware with no alternative.

Fastify — Best Throughput on Node.js

Fastify compiles a serializer from your JSON Schema at startup — 2-3x faster than JSON.stringify. TypeScript is first-class.

import Fastify from 'fastify'
import { Type } from '@sinclair/typebox'

const fastify = Fastify({ logger: true })

fastify.post('/users', {
  schema: {
    body: Type.Object({
      name: Type.String({ minLength: 1 }),
      email: Type.String({ format: 'email' })
    }),
    response: {
      201: Type.Object({ id: Type.String(), name: Type.String(), email: Type.String() })
    }
  }
}, async (request, reply) => {
  // request.body is fully typed from schema
  const user = await db.user.create({ data: request.body })
  reply.status(201).send(user)
})
Enter fullscreen mode Exit fullscreen mode

The schema does two things: validates input at runtime AND compiles the response serializer. One definition, both benefits.

Plugin system with fastify-plugin:

import fp from 'fastify-plugin'

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' })
    request.user = verifyJWT(token)
  })
})
Enter fullscreen mode Exit fullscreen mode

Choose Fastify when: staying on Node.js, schema-first development with TypeBox, need maximum throughput without leaving Node.

Hono — Runs Everywhere

Hono is built on web standards (Request, Response, Headers). The same code runs on Node.js, Bun, Cloudflare Workers, Deno, and AWS Lambda Edge.

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

const app = new Hono()

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

export default app  // same export on Node, Bun, or Cloudflare Workers
Enter fullscreen mode Exit fullscreen mode

Built-in middleware that covers most APIs:

import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { jwt } from 'hono/jwt'
import { rateLimiter } from 'hono-rate-limiter'

app.use('*', logger())
app.use('*', cors({ origin: 'https://myapp.com' }))
app.use('/api/*', jwt({ secret: process.env.JWT_SECRET! }))
Enter fullscreen mode Exit fullscreen mode

RPC with end-to-end type safety (no code generation):

// server.ts
const routes = new Hono()
  .get('/users/:id', async (c) => c.json(await getUser(c.req.param('id'))))

export type AppType = typeof routes

// client.ts
import { hc } from 'hono/client'
const client = hc<AppType>('http://localhost:3000')

const user = await client.users[':id'].$get({ param: { id: '123' } })
// Fully typed response — no API schema file needed
Enter fullscreen mode Exit fullscreen mode

Choose Hono when: multi-runtime (Cloudflare Workers, Bun, Deno), edge deployments, you want tRPC-like type safety without the setup, or you're on Bun and want maximum performance.

The Same Endpoint in All Three

// Express
app.post('/posts', async (req, res) => {
  const parsed = schema.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: PostBody, 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', schema), async (c) => {
  const post = await db.post.create({ data: c.req.valid('json') })
  return c.json(post, 201)
})
Enter fullscreen mode Exit fullscreen mode

Fastify wins on compiled performance. Hono wins on portability and conciseness. Express requires the most boilerplate for equivalent safety.

Decision Framework

Situation Choose
Legacy Express codebase Express
New project, Node.js only, max throughput Fastify
New project, multi-runtime or edge Hono
Using Bun Hono
Need RPC-style type safety Hono
Specific Express middleware with no alternative Express

Full article at stacknotice.com/blog/hono-vs-express-vs-fastify-2026

Top comments (0)