elysia
Elysia is an ergonomic, TypeScript-first web framework optimized for Bun that provides end-to-end type safety, automatic schema validation, and exceptional developer experience. It combines the perfor
Elysia
Elysia is an ergonomic, TypeScript-first web framework optimized for Bun that provides end-to-end type safety, automatic schema validation, and exceptional developer experience. It combines the performance of native HTTP handlers with the expressiveness of modern TypeScript, offering unified type definitions that serve as the single source of truth for both runtime validation and API documentation.
Quick References
| File | Purpose |
|---|---|
src/index.ts | Main entry point with Elysia class and HTTP method handlers |
src/types.ts | Core TypeScript type definitions for Elysia |
README.md | Project overview and basic information |
src/context.ts | Request context and types available in route handlers |
src/schema.ts | Schema validation system integrations |
When to Use
- Building high-performance REST APIs and web services with full TypeScript type safety
- Creating microservices that need automatic request/response validation and error handling
- Developing real-time applications with WebSocket support
- Building APIs that require strict schema validation while maintaining developer ergonomics
- Projects that need OpenAPI/Swagger documentation generation without manual configuration
- Applications requiring cookie management with built-in signing
- Scenarios where you need plugin-based architecture for code organization
Installation
npm install elysia
For Bun users (recommended):
bun install elysia
Best Practices
-
Always define schemas: Use
t.Object()from the included type system to define your request/response schemas for automatic validation and type inference. -
Use guards for reusable validation: Create guards with
.guard()to apply the same schema validation across multiple routes, reducing code duplication. -
Leverage derive/resolve for context augmentation: Use
.derive()to add per-request computed properties to your context instead of passing them manually through handlers. -
Organize with plugins and groups: Use
.group()for route organization (like prefix-based grouping) and.use()for reusable functionality and third-party integrations. -
Enable strict path matching in production: Set
strictPath: truein the Elysia config to prevent ambiguous routing in production environments. -
Use transform for data coercion: Apply
transformhooks to coerce string parameters to proper types before validation runs rather than in your handlers.
Common Patterns
Basic REST API with schema validation:
import { Elysia, t } from 'elysia'
const app = new Elysia()
.get('/', () => 'Hello World')
.post('/users', ({ body }) => {
// body is type-safe with username and password
return { id: 1, username: body.username }
}, {
body: t.Object({
username: t.String(),
password: t.String()
})
})
.listen(3000)
Route grouping with guards:
import { Elysia, t } from 'elysia'
new Elysia()
.guard({
headers: t.Object({
authorization: t.String()
})
}, (app) =>
app
.get('/profile', ({ headers }) => headers.authorization)
.delete('/account/:id', ({ params }) => ({ success: true }), {
params: t.Object({ id: t.Number() })
})
)
.listen(3000)
State management with derive:
import { Elysia } from 'elysia'
new Elysia()
.state('counter', 0)
.derive(({ store }) => ({
increment() {
store.counter++
}
}))
.get('/', ({ increment, store }) => {
increment()
return { count: store.counter }
})
.listen(3000)
Lifecycle hooks:
import { Elysia } from 'elysia'
new Elysia()
.onRequest(({ request }) => {
console.log(`${request.method} ${request.url}`)
})
.onParse(async ({ request, contentType }) => {
if (contentType === 'application/json') {
return request.json()
}
})
.onTransform(({ params }) => {
// Coerce string params to numbers
if (params && 'id' in params) params.id = Number(params.id)
})
.onBeforeHandle(({ query }) => {
if (query?.name === 'admin') return 'Unauthorized'
})
.onAfterHandle(({ response }) => {
// Transform response after handler executes
})
.onError(({ error, code }) => {
console.error(error)
if (code === 'NOT_FOUND') return '404 Not Found'
})
.get('/', () => 'Hello')
.listen(3000)
WebSocket support:
import { Elysia } from 'elysia'
const app = new Elysia()
.ws('/ws', {
open(ws) {
ws.subscribe('channel')
},
message(ws, message) {
ws.publish('channel', message)
},
close(ws) {
console.log('Connection closed')
}
})
.get('/publish/:text', ({ params }) => {
app.server!.publish('channel', params.text)
return 'Published'
})
.listen(3000)
Cookie management with signing:
import { Elysia, t } from 'elysia'
const app = new Elysia({
cookie: {
secrets: 'your-secret-key',
sign: ['session']
}
})
.get('/create', ({ cookie: { session } }) => {
session.value = { userId: 123 }
return 'Cookie created'
}, {
cookie: t.Cookie({
session: t.Object({
userId: t.Number()
})
})
})
.get('/read', ({ cookie: { session } }) => {
return session.value?.userId ?? 'Not logged in'
})
.listen(3000)
Plugin usage:
import { Elysia } from 'elysia'
const authPlugin = new Elysia({ name: 'auth-plugin' })
.decorate('isAuthenticated', () => true)
.get('/check', ({ isAuthenticated }) => isAuthenticated())
const mainApp = new Elysia()
.use(authPlugin)
.get('/', () => 'Main app')
.listen(3000)
Response transformation:
import { Elysia } from 'elysia'
new Elysia()
.mapResponse(({ response }) => {
if (typeof response === 'object') {
return new Response(JSON.stringify(response), {
headers: { 'Content-Type': 'application/json' }
})
}
return response
})
.get('/data', () => ({ message: 'Hello' }))
.listen(3000)
File serving:
import { Elysia, file } from 'elysia'
new Elysia()
.get('/image', file('./path/to/image.png'))
.get('/bun-file', () => Bun.file('./path/to/video.mp4'))
.listen(3000)
Error handling:
import { Elysia, NotFoundError, ValidationError } from 'elysia'
new Elysia()
.error({
NOT_FOUND: NotFoundError,
VALIDATION: ValidationError
})
.onError(({ error, set, code }) => {
if (code === 'NOT_FOUND') {
set.status = 404
return { error: 'Resource not found' }
}
if (code === 'VALIDATION') {
set.status = 400
return { error: 'Invalid input' }
}
})
.get('/users/:id', ({ params }) => {
// Access params.id typed as string
return user
})
.listen(3000)
API Quick Reference
| Export | Type | Description |
|---|---|---|
Elysia | Class | Main framework class for creating server instances |
t | Object | TypeBox schema builder for defining request/response schemas |
status | Function | Helper for creating custom status responses |
NotFoundError | Class | Pre-defined 404 error class |
ValidationError | Class | Validation error for schema validation failures |
ParseError | Class | Parse error for body parsing failures |
InternalServerError | Class | Pre-defined 500 error class |
file | Function | Helper for file serving with type inference |
ElysiaCustomStatusResponse | Class | Custom status response with type inference |
Elysia Instance Methods
| Method | Type | Description |
|---|---|---|
get(path, handler, hook?) | Method | Register GET route with optional schema and hooks |
post(path, handler, hook?) | Method | Register POST route with optional schema and hooks |
put(path, handler, hook?) | Method | Register PUT route with optional schema and hooks |
patch(path, handler, hook?) | Method | Register PATCH route with optional schema and hooks |
delete(path, handler, hook?) | Method | Register DELETE route with optional schema and hooks |
options(path, handler, hook?) | Method | Register OPTIONS route with optional schema and hooks |
| `head |