Hono vs ElysiaJS vs Nitro for Lightweight Backends (2026)
TL;DR
Hono is the most versatile: runs everywhere (Node.js, Bun, Cloudflare Workers, Deno, AWS Lambda) and has crossed 1M weekly downloads. ElysiaJS is the fastest Bun framework with genuinely impressive end-to-end type inference (types flow from server to client automatically). Nitro is the backend runtime used by Nuxt, Vinxi, and Analog — it's the infrastructure layer, not an app framework. For most API projects: Hono. For Bun-only maximum performance: ElysiaJS. For building a meta-framework or SSR adapter: Nitro.
Key Takeaways
- Hono: 1.1M downloads/week, runs everywhere (multi-runtime), tRPC-style RPC optional
- ElysiaJS: 200K downloads/week, Bun-native, Eden treaty for E2E types, fastest throughput
- Nitro: 600K downloads/week, meta-framework runtime (used by Nuxt), H3 under the hood
- Performance: ElysiaJS on Bun is fastest (500K req/s), Hono on Bun second, Hono on Node.js solid
- Edge-first: Hono runs on Cloudflare Workers; ElysiaJS is Bun-only; Nitro supports all adapters
Downloads
| Package | Weekly Downloads | Trend |
|---|---|---|
hono | ~1.1M | ↑ Fast growing |
nitro (via nitropack) | ~600K | ↑ Growing |
elysia | ~200K | ↑ Fast growing |
The Post-Express Era: Why These Frameworks Exist
Express has 30M+ weekly downloads and still runs more Node.js servers than any other framework. But it was written in 2010, has no native TypeScript support, runs only on Node.js, and has no concept of the edge runtime model that Cloudflare Workers, Deno Deploy, and Vercel Edge Functions introduced.
The "lightweight backend" generation (Hono, ElysiaJS, Fastify, H3) solves the problems Express was never designed for:
-
TypeScript first. Express types are in
@types/express, added as an afterthought. Request validation, middleware chaining, and response types require manual typing or libraries likets-rest. Hono and ElysiaJS have type-safe routing, request validation, and response inference built in. -
Multi-runtime support. Express depends on Node.js's
httpmodule. Cloudflare Workers, Deno, and Bun have Web Standard APIs (Request,Response,Headers) — not Node.js APIs. Hono was designed around Web Standard APIs from the start: the sameapp.fetch()handler works on any runtime. -
Performance. Express's middleware chain is synchronous and generates significant overhead per request. Hono's routing is O(log n) using a radix tree; Fastify serializes JSON 3x faster using schema-based serialization; ElysiaJS + Bun achieves 500K req/s for simple endpoints. For APIs handling thousands of concurrent requests, framework overhead matters.
-
Bundle size for edge. Cloudflare Workers have a 1MB bundle limit. Express and its typical middleware stack push 500KB+ easily. Hono is ~15KB.
For greenfield APIs in 2026, the choice is no longer Express vs alternatives — it's which alternative. Express only makes sense when using a library with explicit Express middleware dependencies.
Hono: Universal Edge Framework
npm install hono
# Hono works with multiple runtimes — same code, different entry point:
# Node.js: @hono/node-server
# Bun: bun run serve (native)
# Cloudflare Workers: wrangler
# AWS Lambda: @hono/aws-lambda
// src/app.ts — framework-agnostic Hono app:
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { jwt } from 'hono/jwt';
import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { z } from 'zod';
const app = new Hono();
// Middleware:
app.use('*', cors());
app.use('*', logger());
app.use('/api/*', jwt({ secret: process.env.JWT_SECRET! }));
// Route with Zod validation:
const createPostSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().min(1),
tags: z.array(z.string()).optional(),
});
app.post('/api/posts', zValidator('json', createPostSchema), async (c) => {
const { title, content, tags } = c.req.valid('json');
const user = c.get('jwtPayload'); // From JWT middleware
const post = await db.post.create({
data: { title, content, tags, authorId: user.sub },
});
return c.json(post, 201);
});
app.get('/api/posts/:id', async (c) => {
const id = c.req.param('id');
const post = await db.post.findFirst({ where: { id } });
if (!post) return c.json({ error: 'Not found' }, 404);
return c.json(post);
});
export default app;
// Node.js entry point:
import { serve } from '@hono/node-server';
import app from './app';
serve({ fetch: app.fetch, port: 3000 }, (info) => {
console.log(`Server running at http://localhost:${info.port}`);
});
// Cloudflare Workers entry point (same app!):
// wrangler.toml: name = "my-api", main = "src/worker.ts"
import app from './app';
export default app;
Hono RPC (tRPC-style, native)
// Type-safe client that matches your Hono routes:
import { hc } from 'hono/client';
import type { AppType } from './app';
const client = hc<AppType>('http://localhost:3000');
// Fully typed:
const post = await client.api.posts[':id'].$get({ param: { id: '123' } });
const data = await post.json(); // Typed as your response
ElysiaJS: Bun-Native E2E Types
bun add elysia @elysiajs/eden
// src/app.ts:
import { Elysia, t } from 'elysia';
import { jwt } from '@elysiajs/jwt';
import { cors } from '@elysiajs/cors';
const app = new Elysia()
.use(cors())
.use(jwt({ name: 'jwt', secret: process.env.JWT_SECRET! }))
// Schema-first — types inferred automatically:
.post('/api/posts', async ({ body, jwt: jwtInstance, set }) => {
const user = await jwtInstance.verify(body.token);
if (!user) { set.status = 401; return { error: 'Unauthorized' }; }
const post = await db.post.create({
data: { title: body.title, content: body.content, authorId: user.sub as string },
});
return post;
}, {
// Elysia's type system — validation + type inference:
body: t.Object({
title: t.String({ minLength: 1, maxLength: 100 }),
content: t.String({ minLength: 1 }),
token: t.String(),
}),
response: t.Object({
id: t.String(),
title: t.String(),
content: t.String(),
}),
})
.get('/api/posts/:id', async ({ params }) => {
const post = await db.post.findFirst({ where: { id: params.id } });
return post ?? { error: 'Not found' };
}, {
params: t.Object({ id: t.String() }),
});
export type App = typeof app;
export default app;
// Eden treaty — auto-typed client (no codegen needed):
import { treaty } from '@elysiajs/eden';
import type { App } from './app';
const client = treaty<App>('http://localhost:3000');
// Fully typed — TypeScript knows the shape:
const { data, error } = await client.api.posts.post({
title: 'Hello World',
content: 'My first post',
token: authToken,
});
if (data) console.log(data.id); // TypeScript knows this exists
ElysiaJS Performance (Bun)
HTTP throughput (simple JSON endpoint, Bun runtime):
ElysiaJS + Bun: ~520,000 req/s
Hono + Bun: ~460,000 req/s
Hono + Node.js: ~180,000 req/s
Express + Node: ~45,000 req/s
Fastify + Node: ~120,000 req/s
Nitro: The Meta-Framework Runtime
Nitro is the backend runtime under Nuxt, Vinxi, Analog, and SolidStart. You use it through those frameworks, or directly for custom server builds.
npx giget nitro-app my-app
# Or use as part of Nuxt:
# Nitro handles Nuxt's server routes automatically
// Nuxt server route (Nitro under the hood):
// server/api/posts/[id].ts:
import { defineEventHandler, getRouterParam } from 'h3';
// (h3 = Nitro's HTTP toolkit)
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
const post = await db.post.findFirst({ where: { id } });
if (!post) throw createError({ statusCode: 404, message: 'Not found' });
return post;
});
// Nitro standalone (not via Nuxt):
// server/api/posts.ts:
export default defineEventHandler(async (event) => {
const posts = await db.post.findMany({ orderBy: { createdAt: 'desc' } });
return posts;
});
// nitro.config.ts — deploy anywhere:
import { defineNitroConfig } from 'nitropack/config';
export default defineNitroConfig({
preset: 'cloudflare-workers', // or: vercel, aws-lambda, node-server, bun
// Nitro handles: routing, bundling, deployment
// No app framework needed — server routes are files
});
When Is the Right Time to Migrate from Express?
The answer is "when the cost of staying outweighs the cost of migrating." For most existing codebases, that inflection point is:
- You're adding Cloudflare Workers or edge functions. Express won't run there. If even one route needs to run on the edge, you need a multi-runtime framework. Hono is the natural landing spot.
- TypeScript migration. If you're adding TypeScript to an existing Express app, the gap between Express types and fully-inferred Hono types becomes immediately visible. Many teams find it cleaner to rewrite the thin HTTP layer in Hono than to bolt types onto Express middleware chains.
- Performance regression. If
clinic.jsor0xprofiling shows Express middleware overhead in your hot path, it's worth benchmarking Fastify or Hono. The speed difference is real for high-traffic APIs but irrelevant for APIs handling under 1000 req/s.
For new projects, the question is different: there's no migration cost, so "which is better for my use case" applies directly.
When NOT to migrate: If your team has deep Express expertise, your API is stable, and you're not deploying to edge runtimes, the cost of migrating 50+ endpoints outweighs any benefit. Express works. A framework change costs engineering time that could ship features.
Migrating from Express to Hono
Hono is the most Express-compatible alternative — the middleware model is similar and many concepts transfer directly. A typical Express migration:
Express pattern:
app.get('/users/:id', async (req, res) => {
const user = await db.findUser(req.params.id);
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
});
Hono equivalent:
app.get('/users/:id', async (c) => {
const user = await db.findUser(c.req.param('id'));
if (!user) return c.json({ error: 'Not found' }, 404);
return c.json(user);
});
The differences: context (c) replaces (req, res), return value is used instead of res.json(), and c.req.param() replaces req.params. This pattern change takes a day to internalize.
What doesn't migrate: Express middleware that uses Node.js IncomingMessage/ServerResponse directly won't work on Hono running on a non-Node runtime. But most business logic middleware (auth, rate limiting, logging) can be rewritten as Hono middleware in a day.
Comparison Table
| Hono | ElysiaJS | Nitro | |
|---|---|---|---|
| Runtimes | Node, Bun, CF, Deno, Lambda | Bun (Node partial) | All (via presets) |
| E2E types | Via Hono RPC | ✅ Eden treaty | H3 types |
| Performance | High | Highest (Bun) | Varies by preset |
| Edge-ready | ✅ | ❌ | ✅ |
| Middleware | ✅ Rich | ✅ Plugins | ✅ H3 hooks |
| File routing | ❌ | ❌ | ✅ (auto from files) |
| Validation | Zod/Valibot | Built-in (TypeBox) | Manual |
| Framework vs runtime | Framework | Framework | Runtime/infrastructure |
| Use as standalone | ✅ | ✅ | ✅ |
Production Deployment Patterns
Hono on Cloudflare Workers is the most popular deployment pattern for Hono in 2026. The deployment model: one Hono app with multiple routes becomes one Cloudflare Worker. The Worker runs at the edge in 200+ locations globally, has a 10ms CPU budget per request, and costs essentially nothing for typical API volumes. The wrangler.toml configuration is minimal — just point at your entry file.
The limitation: Cloudflare Workers don't have Node.js API access. fs, path, crypto (Node.js version) — these don't exist. Hono apps must use Web Standard APIs: crypto.subtle instead of Node.js crypto, fetch instead of http, etc. Most business logic doesn't need Node.js APIs, but teams porting existing Express apps need to audit dependencies carefully.
ElysiaJS on a dedicated Bun server targets teams building high-throughput APIs that don't need edge distribution. A VPS running Bun + ElysiaJS handles significantly more requests per second than Node.js + Fastify on equivalent hardware. For analytics ingestion APIs, event pipelines, or gaming backends where every millisecond of server cost counts, ElysiaJS + Bun is a compelling choice in 2026.
The tradeoff: Bun's production stability has improved dramatically but is still not as battle-tested as Node.js for long-running processes. Bun 1.0 shipped in September 2023; it's approaching 3 years old as of 2026. Most teams running Bun in production report a smooth experience, but critical infrastructure teams often stay on Node.js for its longer track record.
Nitro with server presets is how full-stack meta-frameworks handle deployability. When you use Nuxt, you get Nitro for free. The NITRO_PRESET=vercel|cloudflare-workers|aws-lambda|node-server environment variable switches the output format. This is Nitro's compelling value proposition for meta-framework authors: write your server logic once, deploy anywhere. The H3 framework underlying Nitro provides low-level primitives that work across all runtimes.
Ecosystem Maturity
Hono is the clear winner on ecosystem maturity among the three. Its middleware ecosystem covers JWT, session, OAuth, rate limiting, caching, CORS, compression, and OpenAPI documentation (with @hono/zod-openapi). Community adapters connect Hono to Drizzle, Prisma, Clerk, and other ecosystem tools.
ElysiaJS has a growing ecosystem of first-party plugins (@elysiajs/cors, @elysiajs/jwt, @elysiajs/swagger), but community plugins are fewer than Hono's. The framework is opinionated about the Bun ecosystem, which narrows the integration surface.
Nitro's middleware story is through H3's hooks system, which is powerful but lower-level. For building application-layer features, teams using Nuxt directly get full access to Nuxt modules (the Nuxt equivalent of plugins). Using Nitro standalone means building more infrastructure yourself.
Documentation quality correlates with community size here: Hono has the best docs, the largest community, and the most Stack Overflow answers. For teams new to this space, that difference matters practically when debugging issues at 2am.
Decision Guide
Choose Hono if:
→ Multi-runtime needed (Node.js + Cloudflare Workers)
→ Building a REST API or edge API
→ Coming from Express (easy migration)
→ Want built-in middleware (JWT, CORS, cache, logger)
→ Production-proven, large community
Choose ElysiaJS if:
→ Bun is your runtime (committed)
→ End-to-end type safety without separate schema (Eden treaty)
→ Maximum throughput is priority
→ Building a full-stack Bun app
Choose Nitro if:
→ Building a meta-framework or SSR app
→ Using Nuxt (it's built-in)
→ Need universal deployment presets
→ File-based server routes (like Next.js but backend-only)
The Broader Lightweight Backend Ecosystem
Beyond these three, the lightweight backend space in 2026 includes:
Fastify (~8M downloads/week) — the most-used alternative to Express. Schema-based JSON serialization using ajv provides 3-4x faster JSON responses than Express. Full TypeScript support through decorators and generics. For Node.js-only APIs, Fastify vs Hono is the real competition, not Express vs anything. Fastify wins on production maturity; Hono wins on multi-runtime support.
H3 (~3M downloads/week) — the underlying HTTP toolkit that powers Nitro. H3 is designed for event-based HTTP handling with full Web Standard compatibility. Most developers use it through Nitro rather than directly, but it's worth knowing it exists as a standalone library.
Elysia's growing ecosystem includes a testing kit (@elysiajs/testing), Swagger documentation generator, and integration with Bun's native SQLite. The framework is betting heavily on the Bun ecosystem maturing further. If Bun achieves Node.js-equivalent stability in 2026-2027, ElysiaJS could see significantly faster growth.
The common thread: all these frameworks adopt Web Standard APIs (Request, Response, Headers) as their foundation, moving away from Node.js-specific abstractions. This convergence reflects the broader shift in JavaScript backend development toward edge-compatible, multi-runtime code.
Testing patterns also differ meaningfully. Hono has a built-in test client (app.request()) that simulates HTTP requests without spinning up a server — this makes unit testing routes fast and straightforward. ElysiaJS's @elysiajs/testing package provides similar functionality. Nitro testing requires either the full Nuxt test utilities or direct testing of the underlying H3 event handlers. For teams that prioritize fast, isolated API route tests, Hono and ElysiaJS have an edge.
The framework choice in 2026 is not just about technical features — it's about community longevity and hiring. Hono, with 1.1M weekly downloads and Cloudflare's implicit endorsement (it's the recommended framework for Cloudflare Workers in their official docs), has the safest community trajectory. ElysiaJS's fate is linked to Bun's adoption curve. Nitro's longevity is backed by the Nuxt team (UnJS) and its role as infrastructure for multiple major frameworks. All three are reasonable production choices; Hono carries the least risk for teams uncertain about the JavaScript runtime landscape.
Hono's Middleware Ecosystem in 2026
One of Hono's practical advantages over newer competitors is its middleware catalog. The hono/middleware built-in collection and the growing @hono/* ecosystem cover the majority of API building requirements without additional dependencies.
Built-in middleware (zero extra packages):
hono/cors— CORS headers with configurable origins, methods, and headershono/jwt— JWT verification with typed payload extractionhono/logger— Request/response logging with timinghono/cache— Edge cache control headershono/compress— Gzip/Brotli compressionhono/csrf— CSRF token validationhono/rate-limiter— Basic rate limiting (in-memory or external)hono/secure-headers— Security headers (CSP, X-Frame-Options, etc.)
Third-party extensions that integrate with Hono's middleware API:
@hono/zod-validator— Zod input validation with automatic 400 responses@hono/trpc-server— Run tRPC routers inside a Hono app@hono/clerk-auth— Clerk authentication middleware@hono/swagger-ui— OpenAPI/Swagger UI generation
This breadth means a team moving from Express to Hono rarely needs to find replacements for their middleware stack. The ecosystem matured quickly because Hono's runtime-agnostic design means the same middleware works on Cloudflare Workers, Node.js, and Bun — there's one implementation to maintain rather than three.
The Bun Bet: ElysiaJS Performance vs Portability Tradeoff
ElysiaJS's performance story is real: 520,000 requests/second in benchmarks is not marketing. But the runtime requirement is a significant constraint that's worth examining carefully before committing.
ElysiaJS is designed for Bun. It uses Bun's native APIs, Bun's HTTP server, and Bun's V8 alternative (JavaScriptCore) for execution. The performance comes from removing Node.js's overhead entirely. When teams hit Bun compatibility issues with Node-specific packages or deployment targets, they can't simply fall back to Node.js — the Bun-specific APIs don't exist in Node.
The practical risk: Bun's npm compatibility is very good (99%+ of packages work) but not perfect. Packages that use native Node.js bindings or Node.js-specific internals may not work. If any critical production dependency doesn't run on Bun, the entire application needs to either find a replacement or move back to Node.js.
ElysiaJS's Node.js support exists but is partial and not the primary target. Teams choosing ElysiaJS are making a Bun-or-nothing bet. For greenfield projects with modern, pure-JS dependencies and Bun deployments, this is fine. For teams with an existing Node.js package ecosystem or deployment infrastructure tied to Node.js, Hono is the safer path.
The performance argument also requires context. At 180,000 req/s on Node.js, Hono is already faster than most teams need. The gap between Hono+Node and ElysiaJS+Bun matters for applications serving millions of requests per day. For typical SaaS APIs handling thousands of requests per day, both are idle 99.9% of the time.
Migration from Express: A Practical Guide
Express remains the most common Node.js framework by installed base — tens of millions of projects use it. The migration path to Hono is the clearest because Hono is the most Express-like of the new generation:
Routing syntax: Express routes (app.get('/users/:id', handler)) translate directly to Hono (app.get('/users/:id', handler)). The main difference is the context object — (req, res) => {} becomes (c) => c.json(data).
Middleware: Express middleware (req, res, next) becomes Hono middleware (c, next) => {}. Most Express middleware has Hono equivalents; some can be adapted.
The migration approach that works best:
- Start with new routes in Hono running alongside your Express app
- Use Hono's
app.mount()to mount your existing Express app for routes you haven't migrated yet - Migrate routes one by one, starting with read-only endpoints
- Once all routes are migrated, remove the Express dependency
This incremental approach avoids the big-bang rewrite risk. Teams that have done this migration report the main surprise is how much simpler the Hono code is — Express's callback-based model accumulates complexity; Hono's c.json() and c.req.json() pattern is consistently cleaner.
For ElysiaJS migrations from Express, the path is more disruptive because the runtime change (Node.js → Bun) is required first. Most teams start fresh rather than migrate.
Performance in Production: What the Numbers Mean
Raw benchmark numbers (req/s) are useful for understanding theoretical throughput, but the practical performance difference between Hono and ElysiaJS disappears above ~500 req/s on a single core — because at that scale, the bottleneck becomes database I/O or external API latency, not framework routing overhead. ElysiaJS's ~3.2M req/s vs Hono's ~2.5M req/s matters for systems handling millions of concurrent connections on minimal hardware — WebSocket servers, gaming backends, real-time notification systems. For typical REST APIs serving hundreds of thousands of users, the difference is noise.
What actually determines production performance for these frameworks is their behavior under sustained load with memory pressure. Hono's minimal allocation per request means stable memory footprint under load. ElysiaJS, running on Bun's V8-equivalent engine with JIT compilation, shows excellent performance characteristics in sustained benchmarks. Nitro's performance depends heavily on the deployment target — Cloudflare Workers has strict CPU time limits per request (50ms on free tier), so Nitro apps optimizing for Workers need to minimize per-request work regardless of framework overhead.
For teams benchmarking framework choices: run your actual application workload, not synthetic benchmarks. A Hono app that queries a database 3 times per request is limited by database latency (5-50ms), not by the 0.3μs Hono routing overhead.
Compare Hono, ElysiaJS, Fastify, and Express on PkgPulse.
The multi-runtime future. JavaScript is fragmenting across runtimes — Node.js, Bun, Deno, and Cloudflare Workers all have meaningful production usage in 2026. The frameworks that abstract over this complexity (Hono via Web Standards, Nitro via presets) are better positioned for the next 3-5 years than frameworks coupled to a single runtime. Even for teams exclusively on Node.js today, choosing a Web Standard-compatible framework reduces future migration risk if they ever adopt edge or alternative runtimes.
For teams choosing between Hono and Express specifically, see our detailed Express vs Hono comparison, which covers the middleware migration path, performance benchmarks, and TypeScript ergonomics in depth.
See also: Fastify vs Hono and Express vs Hono, Hono vs ElysiaJS vs Nitro (2026).
See the live comparison
View hono vs. express on PkgPulse →