Elysia.js Complete Guide | Fast Web Framework for Bun
이 글의 핵심
Elysia is a TypeScript-first web framework for Bun ??faster than Hono and Express with end-to-end type safety via TypeBox. This guide covers building production APIs with routing, validation, middleware, plugins, and WebSocket.
Why Elysia?
Elysia is a TypeScript-first web framework built for Bun:
- Speed: Benchmarks faster than Hono, Fastify, and Express
- Type safety: End-to-end types with TypeBox ??no runtime type errors
- Eden Treaty: Generate type-safe frontend client from your API definition
- Batteries included: Swagger, CORS, JWT, WebSocket ??all plugins
Setup
# Create project (Bun required)
bun create elysia my-api
cd my-api
bun install
bun run dev
Or from scratch:
mkdir my-api && cd my-api
bun init
bun add elysia
1. Basic Routing
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/', () => 'Hello Elysia!')
.get('/hello/:name', ({ params }) => `Hello, ${params.name}!`)
.post('/echo', ({ body }) => body)
.listen(3000)
console.log(`Server running at http://localhost:${app.server?.port}`)
2. Type-Safe Validation
Elysia uses TypeBox (JSON Schema) for request/response validation ??faster than Zod at runtime:
import { Elysia, t } from 'elysia'
const app = new Elysia()
// GET with query params validation
.get('/users', ({ query }) => {
return { page: query.page, limit: query.limit }
}, {
query: t.Object({
page: t.Number({ minimum: 1, default: 1 }),
limit: t.Number({ minimum: 1, maximum: 100, default: 20 }),
search: t.Optional(t.String()),
}),
})
// POST with body validation
.post('/users', async ({ body }) => {
const user = await createUser(body)
return user
}, {
body: t.Object({
name: t.String({ minLength: 2, maxLength: 100 }),
email: t.String({ format: 'email' }),
role: t.Union([t.Literal('admin'), t.Literal('user')], {
default: 'user',
}),
}),
// Response type validation + docs generation
response: t.Object({
id: t.Number(),
name: t.String(),
email: t.String(),
createdAt: t.String(),
}),
})
.listen(3000)
3. Middleware and Lifecycle Hooks
import { Elysia } from 'elysia'
const app = new Elysia()
// Global hooks
.onRequest(({ request }) => {
console.log(`${request.method} ${request.url}`)
})
.onBeforeHandle(({ request, set }) => {
// Runs before route handler
const token = request.headers.get('Authorization')
if (!token) {
set.status = 401
return { error: 'Unauthorized' }
}
})
.onAfterHandle(({ response, set }) => {
// Runs after route handler
set.headers['X-Powered-By'] = 'Elysia'
})
.onError(({ error, code, set }) => {
if (code === 'NOT_FOUND') {
set.status = 404
return { error: 'Route not found' }
}
if (code === 'VALIDATION') {
set.status = 400
return { error: 'Validation failed', details: error.message }
}
set.status = 500
return { error: 'Internal server error' }
})
.get('/', () => 'Hello!')
.listen(3000)
4. Plugins ??Modular Architecture
import { Elysia } from 'elysia'
// Define a plugin
const userPlugin = new Elysia({ prefix: '/users' })
.get('/', async () => {
return await db.users.findAll()
})
.get('/:id', async ({ params, set }) => {
const user = await db.users.findById(params.id)
if (!user) { set.status = 404; return { error: 'Not found' } }
return user
})
.post('/', async ({ body }) => {
return await db.users.create(body)
})
const postPlugin = new Elysia({ prefix: '/posts' })
.get('/', () => db.posts.findAll())
// Compose plugins
const app = new Elysia()
.use(userPlugin)
.use(postPlugin)
.listen(3000)
// Routes: GET /users, GET /users/:id, POST /users, GET /posts
Built-in plugins
bun add @elysiajs/cors @elysiajs/jwt @elysiajs/swagger @elysiajs/bearer
import { Elysia } from 'elysia'
import { cors } from '@elysiajs/cors'
import { jwt } from '@elysiajs/jwt'
import { swagger } from '@elysiajs/swagger'
import { bearer } from '@elysiajs/bearer'
const app = new Elysia()
.use(cors({
origin: ['https://myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}))
.use(swagger({
documentation: {
info: { title: 'My API', version: '1.0.0' },
},
}))
// Auto-generates Swagger UI at /swagger
.use(jwt({
name: 'jwt',
secret: process.env.JWT_SECRET!,
}))
.use(bearer())
// Extracts Bearer token from Authorization header
.listen(3000)
5. JWT Authentication
import { Elysia, t } from 'elysia'
import { jwt } from '@elysiajs/jwt'
const app = new Elysia()
.use(jwt({ name: 'jwt', secret: process.env.JWT_SECRET! }))
.post('/auth/login', async ({ body, jwt, set }) => {
const user = await db.users.findByEmail(body.email)
if (!user || !await verifyPassword(body.password, user.passwordHash)) {
set.status = 401
return { error: 'Invalid credentials' }
}
const token = await jwt.sign({
sub: user.id,
role: user.role,
exp: Math.floor(Date.now() / 1000) + 3600,
})
return { token, user: { id: user.id, email: user.email } }
}, {
body: t.Object({
email: t.String({ format: 'email' }),
password: t.String({ minLength: 8 }),
}),
})
// Protected routes
.guard(
{
beforeHandle: async ({ jwt, bearer, set }) => {
const payload = await jwt.verify(bearer)
if (!payload) {
set.status = 401
return { error: 'Unauthorized' }
}
},
},
(app) => app
.get('/api/me', async ({ jwt, bearer }) => {
const payload = await jwt.verify(bearer)
return await db.users.findById(payload!.sub as string)
})
.get('/api/dashboard', () => ({ data: 'protected' }))
)
.listen(3000)
6. WebSocket
import { Elysia, t } from 'elysia'
const clients = new Set<{ send: Function }>()
const app = new Elysia()
.ws('/ws/chat', {
// Validate incoming messages
body: t.Object({
type: t.Union([t.Literal('message'), t.Literal('join')]),
text: t.Optional(t.String()),
room: t.Optional(t.String()),
}),
open(ws) {
clients.add(ws)
ws.send({ type: 'connected', clientCount: clients.size })
},
message(ws, data) {
if (data.type === 'message') {
// Broadcast to all connected clients
for (const client of clients) {
client.send({ type: 'message', text: data.text, from: ws.id })
}
}
},
close(ws) {
clients.delete(ws)
},
})
.listen(3000)
7. Eden Treaty ??End-to-End Type Safety
Eden Treaty generates a type-safe client from your Elysia API:
bun add elysia @elysiajs/eden
// Server: export the app type
const app = new Elysia()
.get('/users', () => [{ id: 1, name: 'Alice' }])
.post('/users', ({ body }: { body: { name: string; email: string } }) => ({
id: 2,
...body,
}), {
body: t.Object({ name: t.String(), email: t.String() }),
})
.listen(3000)
export type App = typeof app
// Client: fully typed
import { treaty } from '@elysiajs/eden'
import type { App } from '../server'
const api = treaty<App>('http://localhost:3000')
// Autocomplete + type checking
const { data: users } = await api.users.get()
// data: { id: number; name: string }[]
const { data: newUser } = await api.users.post({
name: 'Bob',
email: 'bob@example.com',
})
// newUser: { id: number; name: string; email: string }
// Type error at compile time if wrong:
// await api.users.post({ name: 'Bob' }) // ??missing email
8. Context ??Shared State
import { Elysia } from 'elysia'
// Decorate context with shared values
const app = new Elysia()
.decorate('db', new Database())
.decorate('logger', new Logger())
.derive(({ request }) => ({
requestId: crypto.randomUUID(),
ip: request.headers.get('x-forwarded-for') ?? 'unknown',
}))
// db, logger, requestId, ip available in all handlers
.get('/users', ({ db, logger, requestId }) => {
logger.info(`[${requestId}] Fetching users`)
return db.users.findAll()
})
.listen(3000)
Elysia vs Hono vs Fastify
| Elysia | Hono | Fastify | |
|---|---|---|---|
| Runtime | Bun (+ adapters) | Any | Node.js |
| Speed | Fastest (Bun) | Very fast | Fast |
| Type safety | End-to-end (Eden) | Routes only | Routes only |
| Validation | TypeBox (built-in) | Zod (plugin) | Ajv (built-in) |
| WebSocket | Built-in | Via adapter | Via plugin |
| Swagger | Plugin | Plugin | Plugin |
| Ecosystem | Growing | Large | Mature |
Key Takeaways
- Elysia = Bun-native, TypeScript-first, fastest REST framework in benchmarks
- TypeBox validation ??schema validation with zero extra config, generates Swagger docs automatically
- Plugins ??
cors,jwt,swagger,bearerall built by the Elysia team - Eden Treaty ??end-to-end type safety like tRPC but for REST APIs
- WebSocket ??first-class, with typed messages
- Lifecycle hooks ??
onRequest,onBeforeHandle,onAfterHandle,onErrorfor middleware
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Elysia is a TypeScript-first web framework for Bun ??faster than Hono and Express with end-to-end type safety via TypeBox. This guide covers building production APIs with routing, validation, middleware, plugins, and WebSocket.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- Hono Framework Guide | Ultra-Fast Edge Web Framework
- Complete Bun Complete Guide | Ultra-Fast JavaScript Runtime
- TypeScript 5 Complete Guide | Decorators· satisfies
Keywords Covered in This Article (Related Search Terms)
This article covers Elysia, Bun, TypeScript, Web-API, Backend, Performance.