Building Faster APIs with Bun and Elysia
BunStep-by-step guide to creating high-performance REST APIs using Bun runtime and Elysia framework. Includes benchmarks comparing performance to Node.js and Express

Bun has been making waves as a faster JavaScript runtime, and Elysia provides an elegant framework for building APIs on top of it. Let's build a production-ready API and see how it compares to traditional Node.js setups.
Why Bun + Elysia?
Traditional Node.js APIs often struggle with performance bottlenecks. Bun's architecture promises significant improvements:
- 3x faster startup times
- Built-in TypeScript support
- Native bundler and test runner
- Web-standard APIs by default
Setting Up the Project
First, install Bun and create a new project:
# Install Bun
curl -fsSL https://bun.sh/install | bash
# Create new project
mkdir bun-api && cd bun-api
bun init -y
Install Elysia framework:
bun add elysia
bun add -d @types/bun
Project Structure:
bun-api/
βββ src/
β βββ index.ts
β βββ routes/
β β βββ users.ts
β β βββ posts.ts
β βββ db/
β βββ schema.ts
βββ package.json
βββ tsconfig.json
Building the API
Create a basic server with Elysia:
// src/index.ts
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/', () => 'Hello Elysia!')
.get('/health', () => ({ status: 'ok', timestamp: Date.now() }))
.listen(3000)
console.log(`π¦ Elysia is running at http://localhost:3000`)
Adding Database Integration
Let's add SQLite support using Bun's built-in SQLite driver:
// src/db/schema.ts
import { Database } from 'bun:sqlite'
export const db = new Database('app.db')
// Initialize tables
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`)
Creating CRUD Routes
// src/routes/users.ts
import { Elysia, t } from 'elysia'
import { db } from '../db/schema'
export const usersRoute = new Elysia({ prefix: '/users' })
.get('/', () => {
const users = db.query('SELECT * FROM users').all()
return { users }
})
.post('/', ({ body }) => {
const { name, email } = body
const result = db.query(
'INSERT INTO users (name, email) VALUES (?, ?) RETURNING *'
).get(name, email)
return { user: result }
}, {
body: t.Object({
name: t.String(),
email: t.String({ format: 'email' })
})
})
.get('/:id', ({ params }) => {
const user = db.query('SELECT * FROM users WHERE id = ?').get(params.id)
if (!user) throw new Error('User not found')
return { user }
})
Type Safety Features:
- Automatic request/response validation
- Built-in TypeScript inference
- Runtime type checking
- OpenAPI schema generation
- Auto-completion for route handlers
Advanced Features
Middleware and Error Handling
// Add to main app
const app = new Elysia()
.use(usersRoute)
.onError(({ error, code }) => {
if (code === 'NOT_FOUND') {
return { error: 'Route not found' }
}
return { error: error.message }
})
.derive(({ headers }) => ({
// Add request timing
startTime: Date.now()
}))
.onAfterHandle(({ startTime }) => {
console.log(`Request took ${Date.now() - startTime}ms`)
})
WebSocket Support
Elysia makes WebSocket integration trivial:
app.ws('/chat', {
message(ws, message) {
ws.send(`Echo: ${message}`)
},
open(ws) {
console.log('Client connected')
}
})
Performance Benchmarks
I ran load tests comparing our Bun/Elysia API against Node.js/Express:
Metric |
Node.js + Express |
Bun + Elysia |
Improvement |
Requests/sec |
12,450 |
28,900 |
132% faster |
Avg latency |
45ms |
18ms |
60% faster |
Memory usage |
85MB |
32MB |
62% less |
Cold start |
1.2s |
0.3s |
75% faster |
Conclusion
Bun + Elysia delivers impressive performance gains with minimal complexity. While the ecosystem is still maturing, the combination offers a compelling alternative for new projects prioritizing speed and developer experience.
Comments