bunWay vs Elysia: Which Bun Framework Should You Choose?
TL;DR: Both are excellent Bun frameworks with different philosophies. Elysia offers maximum performance and end-to-end type safety with a new API. bunWay offers Express compatibility with zero migration effort. Choose based on your priorities.
You've decided to use Bun. Smart choice—it's fast, has great DX, and the ecosystem is maturing quickly.
But now you need a web framework. Two names keep coming up: Elysia and bunWay.
Let's compare them honestly.
The Philosophies
Elysia: "Reimagine web frameworks for Bun"
Elysia is built from scratch for Bun. It doesn't try to be compatible with anything—it optimizes purely for Bun's strengths.
Key principles:
- End-to-end type safety
- Maximum performance
- Declarative, functional API
- Plugin-based architecture
bunWay: "Express API at Bun speed"
bunWay recreates the Express experience on Bun. It prioritizes familiarity and migration ease over reinvention.
Key principles:
- Express API compatibility
- Zero-rewrite migration
- Built-in middleware
- Familiar patterns
Quick Comparison
| Feature | Elysia | bunWay |
| API Style | New (functional, typed) | Express-compatible |
| Learning Curve | Medium | Low (if you know Express) |
| Type Safety | End-to-end inference | Standard TypeScript |
| Performance | Maximum | Near-maximum |
| Migration from Express | Full rewrite | Import changes only |
| Built-in Validation | Yes (TypeBox) | Manual |
| Ecosystem | Elysia plugins | Express-style middleware |
Code Comparison
Basic Route
Elysia:
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/users/:id', ({ params: { id } }) => ({
id,
name: `User ${id}`
}))
.listen(3000)
bunWay:
import { bunway, json } from 'bunway'
const app = bunway()
app.use(json())
app.get('/users/:id', (req, res) => {
res.json({ id: req.params.id, name: `User ${req.params.id}` })
})
app.listen(3000)
Middleware
Elysia:
import { Elysia } from 'elysia'
const app = new Elysia()
.derive(({ headers }) => {
const auth = headers.authorization
return { userId: validateToken(auth) }
})
.get('/profile', ({ userId }) => {
return getUser(userId)
})
bunWay:
import { bunway } from 'bunway'
const app = bunway()
const authMiddleware = (req, res, next) => {
const auth = req.headers.authorization
req.userId = validateToken(auth)
next()
}
app.get('/profile', authMiddleware, (req, res) => {
res.json(getUser(req.userId))
})
Validation
Elysia (built-in TypeBox):
import { Elysia, t } from 'elysia'
const app = new Elysia()
.post('/users', ({ body }) => createUser(body), {
body: t.Object({
name: t.String(),
email: t.String({ format: 'email' }),
age: t.Number({ minimum: 18 })
})
})
bunWay (manual or with Zod):
import { bunway, json } from 'bunway'
import { z } from 'zod'
const app = bunway()
app.use(json())
const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number().min(18)
})
app.post('/users', (req, res) => {
const result = userSchema.safeParse(req.body)
if (!result.success) {
return res.status(400).json({ errors: result.error.issues })
}
res.json(createUser(result.data))
})
WebSockets
Elysia:
import { Elysia } from 'elysia'
const app = new Elysia()
.ws('/chat', {
message(ws, message) {
ws.send(`Echo: ${message}`)
}
})
bunWay:
import { bunway } from 'bunway'
const app = bunway()
app.ws('/chat', {
message(ws, message) {
ws.send(`Echo: ${message}`)
}
})
Performance
Both frameworks are fast because Bun is fast. The framework overhead is minimal compared to the runtime gains.
Realistic benchmarks (JSON response):
| Framework | Requests/sec | Latency (p99) |
| Elysia | ~95,000 | 1.2ms |
| bunWay | ~85,000 | 1.4ms |
| Raw Bun.serve | ~110,000 | 0.9ms |
Elysia is ~10% faster in benchmarks. In real applications with database calls and business logic, this difference becomes negligible.
Choose based on ergonomics, not benchmarks. Both are "fast enough."
Type Safety
Elysia's End-to-End Types
Elysia's killer feature is automatic type inference:
import { Elysia, t } from 'elysia'
const app = new Elysia()
.post('/users', ({ body }) => {
// body is automatically typed as { name: string, age: number }
return { id: 1, ...body }
}, {
body: t.Object({
name: t.String(),
age: t.Number()
})
})
// Client-side with Eden (Elysia's client)
const client = treaty<typeof app>('localhost:3000')
const { data } = await client.users.post({
name: 'Alice', // TypeScript knows this is required
age: 25 // TypeScript knows this is required
})
// data is typed as { id: number, name: string, age: number }
This is genuine end-to-end type safety. Your API contract is enforced at compile time.
bunWay's Standard TypeScript
bunWay uses standard TypeScript:
import { bunway, json, Request, Response } from 'bunway'
interface CreateUserBody {
name: string
age: number
}
const app = bunway()
app.use(json())
app.post('/users', (req: Request, res: Response) => {
const body = req.body as CreateUserBody // Manual type assertion
res.json({ id: 1, ...body })
})
You get TypeScript benefits, but need to manage types manually. No automatic inference across client/server boundaries.
Migration Effort
From Express to bunWay
- import express from 'express'
- import cors from 'cors'
+ import { bunway, cors, json } from 'bunway'
- const app = express()
+ const app = bunway()
- app.use(express.json())
+ app.use(json())
// Routes stay EXACTLY the same
app.get('/users/:id', (req, res) => {
res.json({ id: req.params.id })
})
Effort: Minutes to hours. Change imports, keep everything else.
From Express to Elysia
- import express from 'express'
- const app = express()
- app.use(express.json())
- app.get('/users/:id', (req, res) => {
- res.json({ id: req.params.id })
- })
- app.listen(3000)
+ import { Elysia } from 'elysia'
+ const app = new Elysia()
+ .get('/users/:id', ({ params: { id } }) => ({ id }))
+ .listen(3000)
Effort: Days to weeks. Every route, middleware, and pattern changes.
When to Choose Elysia
Choose Elysia if:
- You're starting fresh — No existing Express code to migrate
- Type safety is critical — End-to-end types catch bugs at compile time
- You want built-in validation — TypeBox integration is seamless
- Performance is paramount — Every microsecond matters
- You like functional patterns — Chained, declarative API
Elysia shines for new projects where you can fully embrace its patterns.
When to Choose bunWay
Choose bunWay if:
- You have Express code — Migrate in minutes, not weeks
- Your team knows Express — No learning curve for the API
- You want familiar patterns —
req,res,nextfeel like home - You need Express middleware patterns — Custom middleware works unchanged
- Migration speed matters — Get Bun benefits immediately
bunWay shines when you want Bun's speed without rewriting your application.
The Honest Trade-offs
Elysia Trade-offs
Pros:
- Maximum type safety
- Slightly better raw performance
- Modern, clean API design
- Built-in everything (validation, swagger, etc.)
Cons:
- Learning curve for new API patterns
- Full rewrite from Express
- Smaller ecosystem (Elysia-specific plugins only)
- Breaking changes as it evolves (still < v1.0 stability)
bunWay Trade-offs
Pros:
- Zero migration effort from Express
- Familiar API (Express knowledge transfers)
- Built-in common middleware
- Lower risk migration
Cons:
- No end-to-end type inference
- Slightly lower raw performance
- Carries some Express design decisions
- Manual validation setup
Can You Use Both?
Yes, actually. Some teams use:
- bunWay for existing services (quick migration)
- Elysia for new services (best patterns)
They coexist fine since both run on Bun.
My Recommendation
For new projects: Try Elysia first. Its type safety and modern patterns are worth learning.
For existing Express apps: Use bunWay. The migration is painless and you get Bun's speed immediately.
If you're unsure: Start with bunWay. You can always rewrite specific services to Elysia later if you want the extra type safety.
Both are excellent frameworks. The "wrong" choice is still a good choice—you're using Bun either way.
Resources
- Elysia: elysiajs.com
- bunWay: bunway.jointops.dev
- Bun: bun.sh
What framework are you using? Let me know in the comments.
Tags: #bun #elysia #bunway #javascript #typescript #webdev #frameworks #comparison