elysiajs-expert

ElysiaJS Expert Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "elysiajs-expert" with this command: npx skills add lammesen/skills/lammesen-skills-elysiajs-expert

ElysiaJS Expert Skill

This skill provides comprehensive expertise for building high-performance, fully type-safe web applications with Elysia on the Bun runtime. It assumes the bun-expert skill is active for Bun-specific patterns (file I/O, SQLite, testing, builds).

When to Use This Skill

  • Building REST APIs with Elysia

  • Implementing type-safe request/response validation with TypeBox

  • Setting up authentication (JWT, Bearer tokens, sessions)

  • Creating WebSocket servers

  • Generating OpenAPI/Swagger documentation

  • Building full-stack applications with Eden Treaty

  • Configuring Elysia plugins (CORS, static files, cron, GraphQL, tRPC)

  • Testing Elysia applications

  • Production deployment optimization

Quick Start

import { Elysia, t } from 'elysia'

const app = new Elysia() .get('/', () => 'Hello Elysia') .get('/user/:id', ({ params }) => User ${params.id}) .post('/user', ({ body }) => body, { body: t.Object({ name: t.String(), email: t.String({ format: 'email' }) }) }) .listen(3000)

export type App = typeof app // Export for Eden client

Core Concepts

Elysia Constructor Options

new Elysia({ name: 'my-app', // Plugin deduplication identifier prefix: '/api', // Route prefix seed: config, // Deduplication checksum seed websocket: { // WebSocket configuration idleTimeout: 30, maxPayloadLength: 16777216 } })

HTTP Methods

app .get('/path', handler) // GET request .post('/path', handler) // POST request .put('/path', handler) // PUT request .delete('/path', handler) // DELETE request .patch('/path', handler) // PATCH request .options('/path', handler) // OPTIONS request .all('/path', handler) // All methods .route('CUSTOM', '/path', handler) // Custom HTTP verb

Path Parameters

.get('/user/:id', ({ params }) => params.id) // Required param .get('/user/:id?', ({ params }) => params.id ?? 'n/a') // Optional param .get('/files/', ({ params }) => params['']) // Wildcard .get('/org/:org/repo/:repo', ({ params }) => params) // Multiple params

Context Object

Every handler receives a context object with:

{ body, // Parsed request body query, // Query string as object params, // Path parameters headers, // Request headers (lowercase keys) cookie, // Cookie jar with get/set store, // Global mutable state set, // Response setters (status, headers) request, // Raw Request object path, // Request path server, // Bun server instance redirect, // Redirect function status, // Status response function // + decorated/derived properties }

Response Patterns

// String .get('/', () => 'Hello')

// JSON (auto-serialized) .get('/json', () => ({ hello: 'world' }))

// Status with response .get('/error', ({ status }) => status(418, "I'm a teapot"))

// Custom headers .get('/custom', ({ set }) => { set.headers['x-powered-by'] = 'Elysia' return 'Hello' })

// Redirect .get('/old', ({ redirect }) => redirect('/new'))

// File import { file } from 'elysia' .get('/image', () => file('image.png'))

// Streaming (generator) .get('/stream', function* () { yield 'Hello ' yield 'World' })

// Async streaming .get('/async', async function* () { for (let i = 0; i < 10; i++) { yield Event ${i}\n await Bun.sleep(100) } })

Lifecycle Hooks (Execution Order)

Request → Parse → Transform → Validation → BeforeHandle → Handler → AfterHandle → MapResponse → AfterResponse

onRequest (Global, Before Routing)

.onRequest(({ request, ip, set, status }) => { // Rate limiting, CORS preflight, request logging if (rateLimiter.exceeded(ip)) return status(429) })

onParse (Body Parser)

.onParse(({ request, contentType }) => { if (contentType === 'application/custom') return request.text() })

// Or specify parser explicitly .post('/', handler, { parse: 'json' }) // 'json' | 'text' | 'formdata' | 'urlencoded' | 'none'

onTransform (Before Validation)

.get('/id/:id', handler, { transform({ params }) { params.id = +params.id // Convert to number before validation } })

derive (Creates Context Properties - Before Validation)

.derive(({ headers }) => ({ bearer: headers.authorization?.startsWith('Bearer ') ? headers.authorization.slice(7) : null })) .get('/protected', ({ bearer }) => bearer)

onBeforeHandle (After Validation)

.onBeforeHandle(({ cookie, status }) => { if (!validateSession(cookie.session.value)) return status(401, 'Unauthorized') })

// Local hook .get('/protected', handler, { beforeHandle({ headers, status }) { if (!headers.authorization) return status(401) } })

resolve (Creates Context Properties - After Validation, Type-Safe)

.guard({ headers: t.Object({ authorization: t.TemplateLiteral('Bearer ${string}') }) }) .resolve(({ headers }) => ({ token: headers.authorization.split(' ')[1], userId: decodeToken(headers.authorization) })) .get('/me', ({ userId }) => userId)

onAfterHandle (Transform Response)

.onAfterHandle(({ responseValue, set }) => { if (isHtml(responseValue)) set.headers['content-type'] = 'text/html' })

mapResponse (Custom Response Mapping)

.mapResponse(({ responseValue, set }) => { set.headers['content-encoding'] = 'gzip' return new Response(Bun.gzipSync(JSON.stringify(responseValue))) })

onError (Error Handling)

import { Elysia, NotFoundError } from 'elysia'

.onError(({ code, error, status }) => { switch(code) { case 'NOT_FOUND': return status(404, 'Not Found') case 'VALIDATION': return { errors: error.all } case 'PARSE': return status(400, 'Invalid body') case 'INTERNAL_SERVER_ERROR': return status(500) default: return new Response(error.toString()) } })

onAfterResponse (Cleanup, Logging)

.onAfterResponse(({ set, request }) => { console.log(${request.method} ${request.url} - ${set.status}) })

Hook Scoping

// Hooks are LOCAL by default in Elysia 1.0+ .onBeforeHandle({ as: 'local' }, handler) // Current instance only .onBeforeHandle({ as: 'scoped' }, handler) // Parent + current + descendants .onBeforeHandle({ as: 'global' }, handler) // All instances

TypeBox Validation (Elysia.t)

Basic Types

import { Elysia, t } from 'elysia'

.post('/user', handler, { body: t.Object({ name: t.String({ minLength: 2, maxLength: 100 }), email: t.String({ format: 'email' }), age: t.Number({ minimum: 0, maximum: 150 }), active: t.Boolean(), tags: t.Array(t.String()), role: t.Union([t.Literal('admin'), t.Literal('user')]), metadata: t.Optional(t.Object({ createdAt: t.String() })) }) })

Schema Locations

.post('/example', handler, { body: t.Object({ ... }), // Request body query: t.Object({ ... }), // Query string params: t.Object({ ... }), // Path params headers: t.Object({ ... }), // Headers (lowercase keys!) cookie: t.Cookie({ ... }), // Cookies response: t.Object({ ... }) // Response validation })

// Response per status code .get('/user', handler, { response: { 200: t.Object({ user: UserSchema }), 400: t.Object({ error: t.String() }), 404: t.Object({ message: t.String() }) } })

Elysia-Specific Types

t.Numeric() // Coerces string to number (query/params) t.File({ format: 'image/*' }) // Single file upload t.Files() // Multiple files t.Cookie({ session: t.String() }, { secure: true, httpOnly: true, sameSite: 'strict' }) t.TemplateLiteral('Bearer ${string}') // Template literal validation t.UnionEnum(['draft', 'published']) // Enum-like union

Custom Error Messages

t.Object({ email: t.String({ format: 'email', error: 'Please provide a valid email' }), age: t.Number({ minimum: 18, error({ value }) { return Age must be 18+ (got ${value}) } }) })

Standard Schema Support (Zod, Valibot)

import { z } from 'zod' import * as v from 'valibot'

.get('/user/:id', handler, { params: z.object({ id: z.coerce.number() }), query: v.object({ name: v.literal('test') }) })

State Management

state (Global Mutable Store)

.state('counter', 0) .state('users', new Map()) .get('/count', ({ store }) => store.counter++)

decorate (Immutable Context Properties)

.decorate('logger', new Logger()) .decorate('version', '1.0.0') .decorate({ db: database, cache: redis }) .get('/', ({ logger, version }) => { logger.log('Request') return version })

Groups and Guards

Groups (Route Prefixes)

.group('/api/v1', app => app .get('/users', handler) .post('/users', handler) )

// With guard configuration .group('/admin', { headers: t.Object({ 'x-admin-key': t.String() }) }, app => app .get('/stats', handler) )

Guards (Shared Validation/Hooks)

.guard({ headers: t.Object({ authorization: t.String() }), beforeHandle: checkAuth }, app => app .get('/protected1', handler1) .get('/protected2', handler2) )

Plugin Architecture

Creating Plugins

// As Elysia instance (recommended) const userPlugin = new Elysia({ name: 'user' }) .state('users', []) .decorate('userService', new UserService()) .get('/users', ({ store }) => store.users)

// As function (access parent config) const configPlugin = (config: Config) => new Elysia({ name: 'config', seed: config }) .decorate('config', config)

// Usage new Elysia() .use(userPlugin) .use(configPlugin({ apiKey: '...' }))

Plugin Scoping

const authPlugin = new Elysia() .onBeforeHandle({ as: 'scoped' }, checkAuth) // Applies to parent too .derive({ as: 'global' }, getUser) // Applies everywhere .as('scoped') // Lift entire plugin

Lazy Loading

.use(import('./heavy-plugin')) await app.modules // Wait for all async plugins

WebSocket Support

Basic WebSocket

.ws('/ws', { message(ws, message) { ws.send('Received: ' + message) } })

Full WebSocket Handler

.ws('/chat', { // Validation body: t.Object({ message: t.String() }), query: t.Object({ room: t.String() }),

open(ws) { const { room } = ws.data.query ws.subscribe(room) ws.publish(room, 'User joined') },

message(ws, { message }) { ws.publish(ws.data.query.room, message) },

close(ws) { ws.publish(ws.data.query.room, 'User left') },

// Authentication beforeHandle({ headers, status }) { if (!headers.authorization) return status(401) } })

WebSocket Methods

ws.send(data) // Send to connection ws.publish(topic, data) // Publish to topic ws.subscribe(topic) // Subscribe to topic ws.unsubscribe(topic) // Unsubscribe ws.close() // Close connection ws.data // Access context (query, params) ws.id // Unique connection ID

Macro Patterns

const authPlugin = new Elysia({ name: 'auth' }) .macro({ isSignIn: { async resolve({ cookie, status }) { if (!cookie.session.value) return status(401) return { user: await getUser(cookie.session.value) } } } })

// Usage .use(authPlugin) .get('/profile', ({ user }) => user, { isSignIn: true })

Official Plugins

@elysiajs/openapi (API Documentation)

import { openapi } from '@elysiajs/openapi'

.use(openapi({ provider: 'scalar', // 'scalar' | 'swagger-ui' | null path: '/docs', documentation: { info: { title: 'My API', version: '1.0.0' }, tags: [{ name: 'User', description: 'User endpoints' }], components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' } } } }, exclude: { methods: ['OPTIONS'], paths: ['/health'] } })) .get('/user', handler, { detail: { tags: ['User'], summary: 'Get user', security: [{ bearerAuth: [] }] } })

@elysiajs/jwt (JSON Web Token)

import { jwt } from '@elysiajs/jwt'

.use(jwt({ name: 'jwt', secret: process.env.JWT_SECRET!, exp: '7d' })) .post('/login', async ({ jwt, body, cookie: { auth } }) => { const token = await jwt.sign({ userId: body.id }) auth.set({ value: token, httpOnly: true, maxAge: 7 * 86400 }) return { token } }) .get('/profile', async ({ jwt, bearer, status }) => { const profile = await jwt.verify(bearer) if (!profile) return status(401) return profile })

@elysiajs/bearer (Token Extraction)

import { bearer } from '@elysiajs/bearer'

.use(bearer()) .get('/protected', ({ bearer, status }) => { if (!bearer) return status(401) return Token: ${bearer} })

@elysiajs/cors (Cross-Origin)

import { cors } from '@elysiajs/cors'

.use(cors({ origin: ['https://app.example.com'], methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, maxAge: 600 }))

@elysiajs/static (Static Files)

import { staticPlugin } from '@elysiajs/static'

.use(staticPlugin({ assets: 'public', prefix: '/static', indexHTML: true }))

@elysiajs/html (HTML/JSX)

import { html } from '@elysiajs/html'

.use(html()) .get('/', () => &#x3C;html> &#x3C;body>&#x3C;h1>Hello&#x3C;/h1>&#x3C;/body> &#x3C;/html>)

@elysiajs/cron (Scheduled Tasks)

import { cron } from '@elysiajs/cron'

.use(cron({ name: 'heartbeat', pattern: '*/10 * * * * *', // Every 10 seconds run() { console.log('tick') } }))

@elysiajs/graphql-yoga (GraphQL)

import { yoga } from '@elysiajs/graphql-yoga'

.use(yoga({ typeDefs: type Query { hello: String }, resolvers: { Query: { hello: () => 'Hello' } }, path: '/graphql' }))

@elysiajs/trpc (tRPC Integration)

import { trpc, compile as c } from '@elysiajs/trpc' import { initTRPC } from '@trpc/server'

const tr = initTRPC.create() const router = tr.router({ greet: tr.procedure .input(c(t.String())) .query(({ input }) => Hello ${input}) })

.use(trpc(router, { endpoint: '/trpc' }))

@elysiajs/server-timing (Performance Headers)

import { serverTiming } from '@elysiajs/server-timing'

.use(serverTiming({ enabled: process.env.NODE_ENV !== 'production' }))

Eden Treaty (Type-Safe Client)

Setup

// server.ts const app = new Elysia() .get('/user/:id', ({ params }) => ({ id: params.id })) .post('/user', ({ body }) => body, { body: t.Object({ name: t.String() }) }) .listen(3000)

export type App = typeof app

// client.ts import { treaty } from '@elysiajs/eden' import type { App } from './server'

const api = treaty<App>('localhost:3000')

Path Syntax

api.index.get() // / api.user({ id: '123' }).get() // /user/123 api.deep.nested.path.get() // /deep/nested/path

Request Parameters

// POST with body const { data, error } = await api.user.post({ name: 'John' })

// With headers/query await api.user.post({ name: 'John' }, { headers: { authorization: 'Bearer token' }, query: { source: 'web' } })

// GET with query await api.users.get({ query: { page: 1, limit: 10 } })

Error Handling

const { data, error, status } = await api.user.post({ name })

if (error) { switch(error.status) { case 400: throw new ValidationError(error.value) case 401: throw new AuthError(error.value) default: throw error.value } }

return data // Type-safe, non-null after error check

WebSocket Client

const chat = api.chat.subscribe()

chat.on('open', () => chat.send('hello')) chat.subscribe(message => console.log(message)) chat.raw // Native WebSocket access

Stream Handling

const { data } = await api.stream.get() for await (const chunk of data) { console.log(chunk) }

Eden Configuration

const api = treaty<App>('localhost:3000', { fetch: { credentials: 'include' }, headers: { authorization: 'Bearer token' }, headers: (path) => ({ /* dynamic headers / }), onRequest: (path, options) => { / modify request / }, onResponse: (response) => { / modify response */ } })

Unit Testing with Eden

import { treaty } from '@elysiajs/eden' import { app } from './server'

// Pass instance directly - no network calls const api = treaty(app)

const { data } = await api.user.post({ name: 'Test' }) expect(data.name).toBe('Test')

Testing Patterns

Unit Testing with bun:test

import { describe, expect, it } from 'bun:test' import { Elysia } from 'elysia'

describe('API', () => { const app = new Elysia() .get('/hello', () => 'Hello') .post('/user', ({ body }) => body)

it('returns hello', async () => { const res = await app.handle(new Request('http://localhost/hello')) expect(await res.text()).toBe('Hello') })

it('creates user', async () => { const res = await app.handle(new Request('http://localhost/user', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Test' }) })) expect(await res.json()).toEqual({ name: 'Test' }) }) })

Testing with Eden

import { treaty } from '@elysiajs/eden' import { app } from './server'

const api = treaty(app)

it('should create user with type safety', async () => { const { data, error } = await api.users.post({ name: 'John', email: 'john@example.com' })

expect(error).toBeNull() expect(data?.name).toBe('John') })

Production Patterns

Recommended Project Structure

src/ ├── modules/ │ ├── auth/ │ │ ├── index.ts # Routes │ │ ├── service.ts # Business logic │ │ └── model.ts # TypeBox schemas │ ├── user/ │ └── product/ ├── shared/ │ ├── middleware/ │ └── utils/ ├── config/ │ └── env.ts ├── index.ts └── server.ts

Module Pattern

// src/modules/user/index.ts import { Elysia } from 'elysia' import { UserService } from './service' import { CreateUserSchema, UserSchema } from './model'

export const userRoutes = new Elysia({ prefix: '/users' }) .post('/', ({ body }) => UserService.create(body), { body: CreateUserSchema, response: UserSchema }) .get('/:id', ({ params }) => UserService.findById(params.id))

Production Build

Compile to binary

bun build --compile --minify-whitespace --minify-syntax
--target bun-linux-x64 --outfile server src/index.ts

Cluster Mode

import cluster from 'node:cluster' import os from 'node:os'

if (cluster.isPrimary) { for (let i = 0; i < os.availableParallelism(); i++) { cluster.fork() } } else { await import('./server') }

Best Practices

  • Always use method chaining - Maintains type inference

  • Name plugins - Enables deduplication

  • Use resolve over derive - When validation is needed first

  • Export type App - For Eden client type safety

  • Use guards - For shared validation across routes

  • Local hooks by default - Explicit as: 'scoped' or as: 'global'

  • Extract services - Outside Elysia for testability

  • Use status() function - For type-safe status responses

References

See <reference/core-api.md> for complete API documentation. See <reference/lifecycle-hooks.md> for hook execution details. See <reference/plugins.md> for all plugin configurations. See <patterns/authentication.md> for auth implementations. See <patterns/testing.md> for testing strategies.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

bun-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

redis-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

zig-expert

No summary provided by upstream source.

Repository SourceNeeds Review
General

postgresql-expert

No summary provided by upstream source.

Repository SourceNeeds Review