elysiajs-expert

安装量: 52
排名: #14146

安装

npx skills add https://github.com/lammesen/skills --skill 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('/', () => `

Hello

`)

@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('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('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 for complete API documentation. See for hook execution details. See for all plugin configurations. See for auth implementations. See for testing strategies.

返回排行榜