Skip to main content

Hono vs Elysia 2026: Speed, DX and Which to Pick for Bun

·PkgPulse Team
0

TL;DR

Hono is the safer, more portable choice. Elysia is faster but Bun-only. Hono runs on Node.js, Bun, Deno, Cloudflare Workers, and AWS Lambda — one codebase, any runtime. Elysia is optimized for Bun specifically and achieves higher raw throughput (up to 2.5M req/s in benchmarks vs Hono's ~1.2M). Choose Hono for edge deployment and runtime portability; choose Elysia if you're all-in on Bun and throughput is the priority.

Key Takeaways

  • Hono: ~1.8M weekly downloads — Elysia: ~500K (npm, March 2026)
  • Elysia is faster in Bun — 2.5M req/s vs Hono's 1.2M req/s (Bun benchmarks)
  • Hono runs everywhere — Cloudflare Workers, Node, Bun, Deno, AWS Lambda
  • Elysia has Eden treaty — end-to-end type safety without code generation
  • Both are TypeScript-first — ergonomic API design with strong inference

The Context: Why These Frameworks Exist

Hono and Elysia emerged as answers to the same question: what does an Express-like API framework look like if you start from scratch with TypeScript, modern runtimes, and web standards in mind?

Express was built in 2010 for Node.js 0.x. It uses callbacks, lacks TypeScript, and can't run on edge runtimes. Hono (2021) and Elysia (2023) are the corrections.

The JavaScript backend landscape in 2026 has fragmented in an interesting way. Node.js still dominates production workloads, but Bun has gained serious traction for new greenfield projects — its package install speed, native TypeScript execution, and built-in test runner remove the friction that made Node.js setup feel bureaucratic. Cloudflare Workers has become the default for API routes that need edge-global latency. Both Hono and Elysia serve this new landscape, but with different philosophies about how to serve it.


Hono: The Portable Choice

// Hono — runs on any runtime
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');
  const user = await db.user.findUnique({ where: { id } });

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

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

// Deploy to Cloudflare Workers
export default app;

// Deploy to Node.js
import { serve } from '@hono/node-server';
serve(app);

// Deploy to Bun
Bun.serve({ fetch: app.fetch });

The "runs on any runtime" claim isn't marketing — it's an architectural consequence of Hono being built on the Request/Response Web API standard. Any runtime that implements the Fetch API spec can run Hono without adaptation. This means the same app object can be exported for Cloudflare Workers, served via @hono/node-server on Node.js, or passed directly to Bun.serve().

For teams building APIs that might need to move between runtimes — or that want the option of edge deployment via Cloudflare Workers — this portability is a real advantage. Migrating an Express app to Cloudflare Workers is essentially a rewrite. Migrating a Hono app from Node.js to Cloudflare Workers is often a few changed import lines.

Hono's middleware ecosystem is mature for a framework its age. The official @hono/* scope includes: Zod request validation, JWT authentication, rate limiting, CORS, compression, static file serving, and Swagger UI generation. The framework also has unofficial adapters for major ORMs and auth libraries.

The type inference in Hono is strong but requires intentional use. Route handlers are typed based on the path parameters and request body validators you configure. c.req.valid('json') in the example above returns a fully typed object derived from the Zod schema — no casting needed. This type narrowing works through the middleware chain.


Elysia: The Bun-Native Choice

// Elysia — Bun-optimized
import { Elysia, t } from 'elysia';

const app = new Elysia()
  .get('/users/:id', async ({ params: { id } }) => {
    const user = await db.user.findUnique({ where: { id } });
    if (!user) throw new Error('Not found');
    return user;
  })
  .post('/users', async ({ body }) => {
    return await db.user.create({ data: body });
  }, {
    body: t.Object({
      name: t.String({ minLength: 1 }),
      email: t.String({ format: 'email' }),
    })
  });

export default app;

Eden Treaty — End-to-End Types Without codegen

// Server
const app = new Elysia()
  .get('/health', () => ({ status: 'ok' as const }))
  .post('/sign-in', ({ body }) => signIn(body), {
    body: t.Object({ email: t.String(), password: t.String() }),
    response: t.Object({ token: t.String() }),
  });

export type App = typeof app;

// Client — fully typed, no codegen
import { treaty } from '@elysiajs/eden';
const client = treaty<App>('localhost:3000');

const { data, error } = await client.health.get();
// data is typed as { status: 'ok' }

const { data: auth } = await client['sign-in'].post({
  email: 'user@example.com',
  password: 'secret',
});
// auth.token is typed as string

Elysia's architecture is optimized specifically for Bun's V8 JIT compilation characteristics. The internal routing uses a radix tree with Bun-specific optimizations that take advantage of Bun's fast JavaScript engine and native TypeScript execution. This is why Elysia benchmarks faster on Bun than Hono — it's not a general optimization but a Bun-specific one. On Node.js, Elysia loses much of this advantage.

Eden Treaty is Elysia's signature differentiator. tRPC achieves end-to-end type safety but requires a specific RPC architecture — the client calls typed procedures, not REST endpoints. Eden Treaty achieves similar type safety while keeping a traditional REST API: the server exposes GET /health, the client calls client.health.get(), and both sides share types through the exported App type. No code generation, no schema compilation step, no build-time tooling required.

This matters more than it might seem. In a large codebase, the friction of maintaining GraphQL schemas or tRPC procedure definitions adds up. Eden Treaty's approach — the types are just TypeScript inference from the route definitions — means the type system stays in sync with the API automatically.

Elysia's validation uses TypeBox (t.Object, t.String) rather than Zod. TypeBox is more performant (2-4x faster validation) and integrates with Elysia's type system more cleanly, but it's less familiar to developers who know Zod. The two APIs are similar enough that learning TypeBox isn't onerous.


Performance Comparison

Both significantly outperform Express and Fastify on Bun:

FrameworkRuntimeReq/s (Hello World)
ElysiaBun~2.5M
HonoBun~1.2M
HonoNode.js~350K
FastifyNode.js~230K
ExpressNode.js~80K

Important caveat: These are synthetic benchmarks measuring framework overhead with minimal request handling. Real-world performance depends on database queries, JSON serialization, middleware overhead, and authentication checks — where the differences between frameworks shrink to 10-20% rather than 10x. A slow database query costs 10ms regardless of whether your framework adds 5µs or 50µs overhead.

The benchmarks are still meaningful for specific cases: WebSocket servers handling thousands of concurrent connections, event-streaming APIs, or caching layers where the database is rarely hit. In those scenarios, raw framework throughput matters.


Ecosystem Comparison

FeatureHonoElysia
Runtime supportNode, Bun, Deno, CF Workers, AWSBun (primary), experimental others
ValidationZod (via middleware)Built-in TypeBox (t.Object)
E2E type safetyhono/client RPCEden Treaty
Auth middleware✅ (JWT, Basic)✅ (plugins)
OpenAPI/Swagger@hono/swagger-ui@elysiajs/swagger
WebSocket support
Weekly downloads~1.8M~500K
GitHub stars~22K~11K
First stable release20212023

Middleware Architecture

Hono's middleware pattern follows Express conventions with a more functional API. Middleware handlers receive the context object c and call next() for chain continuation. The official middleware packages cover most common requirements:

  • @hono/jwt — JWT authentication with automatic header extraction
  • @hono/zod-validator — Zod schema validation for request body, query, and params
  • @hono/cors — CORS headers with flexible configuration
  • @hono/rate-limiter — in-memory rate limiting (works on edge runtimes)
  • @hono/compress — response compression (gzip, brotli)

Elysia uses a "macro" pattern for middleware — plugins that extend the context with new properties and lifecycle hooks. Elysia's derive() mechanism is particularly interesting: it lets you attach computed properties to the request context that are typed end-to-end.

// Elysia — typed context extension
const app = new Elysia()
  .derive(async ({ headers }) => {
    const authHeader = headers.authorization;
    const user = authHeader ? await verifyJWT(authHeader) : null;
    return { user };
  })
  .get('/me', ({ user }) => {
    // user is typed as User | null
    if (!user) throw new Error('Unauthorized');
    return user;
  });

Deployment Patterns

Cloudflare Workers: Hono is the clear winner. It was designed for Workers and is used by Cloudflare internally. Elysia's Workers support is experimental and not recommended for production.

Bun servers: Both work, but Elysia has a more optimized integration. Bun.serve({ fetch: app.fetch }) works for both.

Node.js: Hono via @hono/node-server; Elysia runs on Node.js but with degraded performance compared to Bun.

Docker containers: Both work in containerized Node.js or Bun environments. Bun's Alpine-based Docker images are significantly smaller than Node.js equivalents.


When to Choose

Choose Hono when:

  • You need to deploy to Cloudflare Workers or edge runtimes
  • Runtime portability matters (same code on Node and CF Workers)
  • Your team isn't ready to commit fully to Bun
  • You want the larger community and more middleware options
  • You need production-tested stability for a high-stakes API

Choose Elysia when:

  • You're committed to Bun as your runtime
  • Eden Treaty's end-to-end type safety is compelling
  • You want maximum throughput in a Bun environment
  • You appreciate Elysia's opinionated, batteries-included approach
  • Your API is a monorepo with a frontend that benefits from shared types

Database Integration Patterns

Framework choice affects how cleanly you can integrate with serverless databases. Hono and Elysia have different strengths here, particularly around connection pooling and edge runtime compatibility.

Connecting to Serverless Databases

Both frameworks run in environments where persistent database connections are problematic — serverless functions are stateless, and traditional connection pools (Prisma's connection pool, pg's built-in pooler) leak connections across invocations.

// Hono + Neon (serverless Postgres) — optimal for edge/serverless
import { Hono } from 'hono';
import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';

const app = new Hono();

// Create client per-request (stateless connection via HTTP)
app.get('/users', async (c) => {
  const sql = neon(c.env.DATABASE_URL); // Cloudflare Workers env var
  const db = drizzle(sql);
  const users = await db.select().from(usersTable);
  return c.json(users);
});
// Elysia + Turso (edge SQLite) — ideal for Bun
import { Elysia } from 'elysia';
import { createClient } from '@libsql/client';
import { drizzle } from 'drizzle-orm/libsql';

const client = createClient({
  url: Bun.env.TURSO_DATABASE_URL!,
  authToken: Bun.env.TURSO_AUTH_TOKEN!,
});

const db = drizzle(client); // Module-level, libSQL handles connection reuse

const app = new Elysia()
  .get('/users', async () => {
    return await db.select().from(usersTable);
  });

Neon's HTTP-based connection model is edge-native — it uses fetch() internally, which means the same code works on Cloudflare Workers, Vercel Edge Functions, and Node.js. This makes Neon + Hono a natural pairing for teams deploying the same API codebase to both edge and traditional serverless environments.

Turso's libSQL client creates persistent connections but manages them efficiently for long-running server processes. In a Bun server process (not a serverless function), a module-level createClient() instance performs well. For a deeper comparison of database options including pricing, branching, and edge compatibility, see Turso vs PlanetScale vs Neon 2026.

Middleware for Database Context

Both frameworks support injecting database clients into request context, but with different mechanisms:

// Hono — inject DB via middleware
import { createMiddleware } from 'hono/factory';

const dbMiddleware = createMiddleware(async (c, next) => {
  const sql = neon(c.env.DATABASE_URL);
  c.set('db', drizzle(sql));
  await next();
});

app.use('/api/*', dbMiddleware);
app.get('/api/users', async (c) => {
  const db = c.get('db'); // Typed by middleware
  // ...
});
// Elysia — inject DB via decorate
const app = new Elysia()
  .decorate('db', drizzle(client))
  .get('/users', async ({ db }) => {
    // db is typed automatically — no casting
    return await db.select().from(usersTable);
  });

Elysia's decorate approach integrates with Eden Treaty's type inference — the db context property type flows through to the client SDK automatically. Hono's middleware approach is more flexible but requires explicit type assertions or context variable typing configuration.

Connection Pooling in Production

For high-throughput Node.js deployments (not serverless), both frameworks work with PgBouncer or Neon's built-in pooler. The key: configure max: 1 per serverless instance and rely on the external pooler for actual connection management. This pattern prevents the connection exhaustion that occurs when multiple serverless instances each hold pooled connections simultaneously. For most applications, the built-in HTTP-based clients for Neon and Turso handle this transparently.


Testing, Validation, and Error Handling in Practice

Framework choice affects testing strategy, input validation architecture, and error handling patterns in ways that aren't obvious from benchmark comparisons. Here's how Hono and Elysia differ on the practical side.

Input Validation

Both frameworks have first-class validation support, but with different philosophies. Hono's @hono/zod-validator middleware integrates Zod schema validation into request handlers, following the same pattern used across the JavaScript ecosystem. If you're already using Zod elsewhere in your codebase (Prisma, tRPC, form validation), this consistency reduces cognitive overhead:

Elysia uses its own t object (based on TypeBox/JSON Schema) for validation. The advantage is tighter runtime and TypeScript type integration — Elysia's validation and TypeScript types are generated from the same schema definition. The disadvantage is ecosystem lock-in: you can't easily reuse Zod schemas from other parts of your codebase without a conversion step.

For teams already invested in Zod, Hono's validation story is more ergonomic. For teams starting fresh, Elysia's end-to-end type integration (where validation schema, TypeScript types, and OpenAPI schema are all derived from one definition) is compelling.

Error Handling

Hono's app.onError() provides a global error handler. Route-level errors use standard JavaScript try/catch or can throw HTTPException objects that Hono catches automatically. This follows familiar Express-like patterns and is easy to understand.

Elysia's error handling uses a more explicit .error() lifecycle hook and custom error types. The error() function throws typed errors that Elysia maps to HTTP responses. This is more opinionated but enables better TypeScript inference — error types flow through the same type system as successful responses, which helps with Eden Treaty's client-side type generation.

Testing Strategies

Both frameworks are testable without a running HTTP server. Hono's app.request() method accepts a Request object and returns a Response, making unit tests straightforward:

// Testing a Hono handler without starting a server
const res = await app.request('/api/users/1', {
  method: 'GET',
  headers: { Authorization: 'Bearer test-token' },
});
expect(res.status).toBe(200);
const body = await res.json();
expect(body.id).toBe('1');

Elysia has a similar pattern using .handle():

// Testing an Elysia handler
const response = await app.handle(
  new Request('http://localhost/api/users/1')
);
expect(response.status).toBe(200);

Both approaches integrate well with Vitest or Bun test — no mock frameworks needed for basic route testing. Where they diverge is in testing middleware: Hono's middleware-heavy architecture is testable by composing middleware stacks directly in tests, while Elysia's guard and lifecycle hooks require more careful setup to test in isolation.

OpenAPI and Documentation Generation

Hono has @hono/swagger-ui and @hono/openapi for generating OpenAPI specs from Zod schemas. Elysia's Swagger plugin auto-generates an OpenAPI spec from its TypeBox schema definitions with less configuration. If your API needs to be consumed by clients that use generated SDKs (mobile teams, third-party integrators), Elysia's zero-friction OpenAPI generation is a practical advantage.

Compare Hono and Elysia package health on PkgPulse. Related: Best JavaScript Runtimes 2026 and Best JavaScript Testing Frameworks 2026.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.