elysia

IndexedCommit: 3edbde41 pullsUpdated Jan 29, 2026

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

Install this reference
Reference

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

FilePurpose
src/index.tsMain entry point with Elysia class and HTTP method handlers
src/types.tsCore TypeScript type definitions for Elysia
README.mdProject overview and basic information
src/context.tsRequest context and types available in route handlers
src/schema.tsSchema 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

  1. Always define schemas: Use t.Object() from the included type system to define your request/response schemas for automatic validation and type inference.

  2. Use guards for reusable validation: Create guards with .guard() to apply the same schema validation across multiple routes, reducing code duplication.

  3. Leverage derive/resolve for context augmentation: Use .derive() to add per-request computed properties to your context instead of passing them manually through handlers.

  4. Organize with plugins and groups: Use .group() for route organization (like prefix-based grouping) and .use() for reusable functionality and third-party integrations.

  5. Enable strict path matching in production: Set strictPath: true in the Elysia config to prevent ambiguous routing in production environments.

  6. Use transform for data coercion: Apply transform hooks 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

ExportTypeDescription
ElysiaClassMain framework class for creating server instances
tObjectTypeBox schema builder for defining request/response schemas
statusFunctionHelper for creating custom status responses
NotFoundErrorClassPre-defined 404 error class
ValidationErrorClassValidation error for schema validation failures
ParseErrorClassParse error for body parsing failures
InternalServerErrorClassPre-defined 500 error class
fileFunctionHelper for file serving with type inference
ElysiaCustomStatusResponseClassCustom status response with type inference

Elysia Instance Methods

MethodTypeDescription
get(path, handler, hook?)MethodRegister GET route with optional schema and hooks
post(path, handler, hook?)MethodRegister POST route with optional schema and hooks
put(path, handler, hook?)MethodRegister PUT route with optional schema and hooks
patch(path, handler, hook?)MethodRegister PATCH route with optional schema and hooks
delete(path, handler, hook?)MethodRegister DELETE route with optional schema and hooks
options(path, handler, hook?)MethodRegister OPTIONS route with optional schema and hooks
`head

“Explore distant worlds.”

© 2026 Oscar Gabriel