elysiajs-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ElysiaJS Expert Skill

ElysiaJS 专家技能指南

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).
本技能为基于Bun运行时,使用Elysia构建高性能、完全类型安全的Web应用提供全面的专业指导。它假设已激活
bun-expert
技能,以支持Bun特定的使用模式(文件I/O、SQLite、测试、构建)。

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
  • 使用Elysia构建REST API
  • 基于TypeBox实现类型安全的请求/响应验证
  • 配置认证机制(JWT、Bearer令牌、会话)
  • 创建WebSocket服务器
  • 生成OpenAPI/Swagger文档
  • 结合Eden Treaty构建全栈应用
  • 配置Elysia插件(CORS、静态文件、定时任务、GraphQL、tRPC)
  • 测试Elysia应用
  • 生产环境部署优化

Quick Start

快速开始

typescript
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
typescript
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 // 导出给Eden客户端使用

Core Concepts

核心概念

Elysia Constructor Options

Elysia构造函数选项

typescript
new Elysia({
  name: 'my-app',              // Plugin deduplication identifier
  prefix: '/api',              // Route prefix
  seed: config,                // Deduplication checksum seed
  websocket: {                 // WebSocket configuration
    idleTimeout: 30,
    maxPayloadLength: 16777216
  }
})
typescript
new Elysia({
  name: 'my-app',              // 插件去重标识符
  prefix: '/api',              // 路由前缀
  seed: config,                // 去重校验和种子
  websocket: {                 // WebSocket配置
    idleTimeout: 30,
    maxPayloadLength: 16777216
  }
})

HTTP Methods

HTTP方法

typescript
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
typescript
app
  .get('/path', handler)       // GET请求
  .post('/path', handler)      // POST请求
  .put('/path', handler)       // PUT请求
  .delete('/path', handler)    // DELETE请求
  .patch('/path', handler)     // PATCH请求
  .options('/path', handler)   // OPTIONS请求
  .all('/path', handler)       // 所有方法
  .route('CUSTOM', '/path', handler) // 自定义HTTP动词

Path Parameters

路径参数

typescript
.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
typescript
.get('/user/:id', ({ params }) => params.id)           // 必填参数
.get('/user/:id?', ({ params }) => params.id ?? 'n/a') // 可选参数
.get('/files/*', ({ params }) => params['*'])          // 通配符
.get('/org/:org/repo/:repo', ({ params }) => params)   // 多个参数

Context Object

上下文对象

Every handler receives a context object with:
typescript
{
  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
}
每个处理器都会接收一个上下文对象,包含以下属性:
typescript
{
  body,        // 解析后的请求体
  query,       // 解析为对象的查询字符串
  params,      // 路径参数
  headers,     // 请求头(键为小写)
  cookie,      // 包含get/set方法的Cookie容器
  store,       // 全局可变状态
  set,         // 响应设置器(状态码、响应头)
  request,     // 原始Request对象
  path,        // 请求路径
  server,      // Bun服务器实例
  redirect,    // 重定向函数
  status,      // 状态码响应函数
  // + 装饰/派生的属性
}

Response Patterns

响应模式

typescript
// 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)
  }
})
typescript
// 字符串响应
.get('/', () => 'Hello')

// JSON响应(自动序列化)
.get('/json', () => ({ hello: 'world' }))

// 带状态码的响应
.get('/error', ({ status }) => status(418, "I'm a teapot"))

// 自定义响应头
.get('/custom', ({ set }) => {
  set.headers['x-powered-by'] = 'Elysia'
  return 'Hello'
})

// 重定向
.get('/old', ({ redirect }) => redirect('/new'))

// 文件响应
import { file } from 'elysia'
.get('/image', () => file('image.png'))

// 流式响应(生成器)
.get('/stream', function* () {
  yield 'Hello '
  yield 'World'
})

// 异步流式响应
.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
Request → Parse → Transform → Validation → BeforeHandle → Handler → AfterHandle → MapResponse → AfterResponse

onRequest (Global, Before Routing)

onRequest(全局,路由匹配前)

typescript
.onRequest(({ request, ip, set, status }) => {
  // Rate limiting, CORS preflight, request logging
  if (rateLimiter.exceeded(ip)) return status(429)
})
typescript
.onRequest(({ request, ip, set, status }) => {
  // 限流、CORS预检、请求日志
  if (rateLimiter.exceeded(ip)) return status(429)
})

onParse (Body Parser)

onParse(请求体解析)

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

// Or specify parser explicitly
.post('/', handler, { parse: 'json' }) // 'json' | 'text' | 'formdata' | 'urlencoded' | 'none'
typescript
.onParse(({ request, contentType }) => {
  if (contentType === 'application/custom')
    return request.text()
})

// 或者显式指定解析器
.post('/', handler, { parse: 'json' }) // 'json' | 'text' | 'formdata' | 'urlencoded' | 'none'

onTransform (Before Validation)

onTransform(验证前)

typescript
.get('/id/:id', handler, {
  transform({ params }) {
    params.id = +params.id // Convert to number before validation
  }
})
typescript
.get('/id/:id', handler, {
  transform({ params }) {
    params.id = +params.id // 验证前转换为数字
  }
})

derive (Creates Context Properties - Before Validation)

derive(创建上下文属性 - 验证前)

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

onBeforeHandle (After Validation)

onBeforeHandle(验证后)

typescript
.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)
  }
})
typescript
.onBeforeHandle(({ cookie, status }) => {
  if (!validateSession(cookie.session.value))
    return status(401, 'Unauthorized')
})

// 局部钩子
.get('/protected', handler, {
  beforeHandle({ headers, status }) {
    if (!headers.authorization) return status(401)
  }
})

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

resolve(创建上下文属性 - 验证后,类型安全)

typescript
.guard({
  headers: t.Object({ authorization: t.TemplateLiteral('Bearer ${string}') })
})
.resolve(({ headers }) => ({
  token: headers.authorization.split(' ')[1],
  userId: decodeToken(headers.authorization)
}))
.get('/me', ({ userId }) => userId)
typescript
.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(转换响应)

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

mapResponse (Custom Response Mapping)

mapResponse(自定义响应映射)

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

onError (Error Handling)

onError(错误处理)

typescript
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())
  }
})
typescript
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(清理、日志)

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

Hook Scoping

钩子作用域

typescript
// 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
typescript
// 在Elysia 1.0+中,钩子默认是局部的
.onBeforeHandle({ as: 'local' }, handler)   // 仅当前实例生效
.onBeforeHandle({ as: 'scoped' }, handler)  // 父实例 + 当前实例 + 子实例生效
.onBeforeHandle({ as: 'global' }, handler)  // 所有实例生效

TypeBox Validation (Elysia.t)

TypeBox验证(Elysia.t)

Basic Types

基础类型

typescript
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() }))
  })
})
typescript
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

验证规则位置

typescript
.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() })
  }
})
typescript
.post('/example', handler, {
  body: t.Object({ ... }),       // 请求体验证
  query: t.Object({ ... }),      // 查询字符串验证
  params: t.Object({ ... }),     // 路径参数验证
  headers: t.Object({ ... }),    // 请求头验证(键为小写!)
  cookie: t.Cookie({ ... }),     // Cookie验证
  response: t.Object({ ... })    // 响应验证
})

// 按状态码配置响应验证
.get('/user', handler, {
  response: {
    200: t.Object({ user: UserSchema }),
    400: t.Object({ error: t.String() }),
    404: t.Object({ message: t.String() })
  }
})

Elysia-Specific Types

Elysia专属类型

typescript
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
typescript
t.Numeric()                           // 将字符串转换为数字(查询/路径参数)
t.File({ format: 'image/*' })         // 单文件上传
t.Files()                             // 多文件上传
t.Cookie({ session: t.String() }, {
  secure: true, httpOnly: true, sameSite: 'strict'
})
t.TemplateLiteral('Bearer ${string}') // 模板字面量验证
t.UnionEnum(['draft', 'published'])   // 枚举风格联合类型

Custom Error Messages

自定义错误消息

typescript
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})`
    }
  })
})
typescript
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)

标准Schema支持(Zod、Valibot)

typescript
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') })
})
typescript
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(全局可变存储)

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

decorate (Immutable Context Properties)

decorate(不可变上下文属性)

typescript
.decorate('logger', new Logger())
.decorate('version', '1.0.0')
.decorate({ db: database, cache: redis })
.get('/', ({ logger, version }) => {
  logger.log('Request')
  return version
})
typescript
.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)

分组(路由前缀)

typescript
.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)
)
typescript
.group('/api/v1', app => app
  .get('/users', handler)
  .post('/users', handler)
)

// 结合守卫配置
.group('/admin', {
  headers: t.Object({ 'x-admin-key': t.String() })
}, app => app
  .get('/stats', handler)
)

Guards (Shared Validation/Hooks)

守卫(共享验证/钩子)

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

Plugin Architecture

插件架构

Creating Plugins

创建插件

typescript
// 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: '...' }))
typescript
// 推荐方式:作为Elysia实例
const userPlugin = new Elysia({ name: 'user' })
  .state('users', [])
  .decorate('userService', new UserService())
  .get('/users', ({ store }) => store.users)

// 函数方式(可访问父实例配置)
const configPlugin = (config: Config) =>
  new Elysia({ name: 'config', seed: config })
    .decorate('config', config)

// 使用方式
new Elysia()
  .use(userPlugin)
  .use(configPlugin({ apiKey: '...' }))

Plugin Scoping

插件作用域

typescript
const authPlugin = new Elysia()
  .onBeforeHandle({ as: 'scoped' }, checkAuth) // Applies to parent too
  .derive({ as: 'global' }, getUser)           // Applies everywhere
  .as('scoped')                                 // Lift entire plugin
typescript
const authPlugin = new Elysia()
  .onBeforeHandle({ as: 'scoped' }, checkAuth) // 同时作用于父实例
  .derive({ as: 'global' }, getUser)           // 作用于所有实例
  .as('scoped')                                 // 提升整个插件的作用域

Lazy Loading

懒加载

typescript
.use(import('./heavy-plugin'))
await app.modules // Wait for all async plugins
typescript
.use(import('./heavy-plugin'))
await app.modules // 等待所有异步插件加载完成

WebSocket Support

WebSocket支持

Basic WebSocket

基础WebSocket

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

Full WebSocket Handler

完整WebSocket处理器

typescript
.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)
  }
})
typescript
.ws('/chat', {
  // 验证规则
  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')
  },

  // 认证逻辑
  beforeHandle({ headers, status }) {
    if (!headers.authorization) return status(401)
  }
})

WebSocket Methods

WebSocket方法

typescript
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
typescript
ws.send(data)              // 向连接发送数据
ws.publish(topic, data)    // 向主题发布数据
ws.subscribe(topic)        // 订阅主题
ws.unsubscribe(topic)      // 取消订阅
ws.close()                 // 关闭连接
ws.data                    // 访问上下文(查询、参数)
ws.id                      // 唯一连接ID

Macro Patterns

宏模式

typescript
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 })
typescript
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) }
      }
    }
  })

// 使用方式
.use(authPlugin)
.get('/profile', ({ user }) => user, { isSignIn: true })

Official Plugins

官方插件

@elysiajs/openapi (API Documentation)

@elysiajs/openapi(API文档)

typescript
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: [] }]
  }
})
typescript
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)

@elysiajs/jwt(JSON Web Token)

typescript
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
})
typescript
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)

@elysiajs/bearer(令牌提取)

typescript
import { bearer } from '@elysiajs/bearer'

.use(bearer())
.get('/protected', ({ bearer, status }) => {
  if (!bearer) return status(401)
  return `Token: ${bearer}`
})
typescript
import { bearer } from '@elysiajs/bearer'

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

@elysiajs/cors (Cross-Origin)

@elysiajs/cors(跨域资源共享)

typescript
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
}))
typescript
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)

@elysiajs/static(静态文件服务)

typescript
import { staticPlugin } from '@elysiajs/static'

.use(staticPlugin({
  assets: 'public',
  prefix: '/static',
  indexHTML: true
}))
typescript
import { staticPlugin } from '@elysiajs/static'

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

@elysiajs/html (HTML/JSX)

@elysiajs/html(HTML/JSX支持)

typescript
import { html } from '@elysiajs/html'

.use(html())
.get('/', () => `
  <html>
    <body><h1>Hello</h1></body>
  </html>
`)
typescript
import { html } from '@elysiajs/html'

.use(html())
.get('/', () => `
  <html>
    <body><h1>Hello</h1></body>
  </html>
`)

@elysiajs/cron (Scheduled Tasks)

@elysiajs/cron(定时任务)

typescript
import { cron } from '@elysiajs/cron'

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

.use(cron({
  name: 'heartbeat',
  pattern: '*/10 * * * * *', // 每10秒执行一次
  run() { console.log('tick') }
}))

@elysiajs/graphql-yoga (GraphQL)

@elysiajs/graphql-yoga(GraphQL集成)

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

.use(yoga({
  typeDefs: `type Query { hello: String }`,
  resolvers: { Query: { hello: () => 'Hello' } },
  path: '/graphql'
}))
typescript
import { yoga } from '@elysiajs/graphql-yoga'

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

@elysiajs/trpc (tRPC Integration)

@elysiajs/trpc(tRPC集成)

typescript
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' }))
typescript
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)

@elysiajs/server-timing(性能监控头)

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

.use(serverTiming({
  enabled: process.env.NODE_ENV !== 'production'
}))
typescript
import { serverTiming } from '@elysiajs/server-timing'

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

Eden Treaty (Type-Safe Client)

Eden Treaty(类型安全客户端)

Setup

配置

typescript
// 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')
typescript
// 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

路径语法

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

Request Parameters

请求参数

typescript
// 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 } })
typescript
// 带请求体的POST请求
const { data, error } = await api.user.post({ name: 'John' })

// 带请求头和查询参数
await api.user.post({ name: 'John' }, {
  headers: { authorization: 'Bearer token' },
  query: { source: 'web' }
})

// 带查询参数的GET请求
await api.users.get({ query: { page: 1, limit: 10 } })

Error Handling

错误处理

typescript
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
typescript
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 // 错误检查后,data为类型安全的非空值

WebSocket Client

WebSocket客户端

typescript
const chat = api.chat.subscribe()

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

chat.on('open', () => chat.send('hello'))
chat.subscribe(message => console.log(message))
chat.raw // 原生WebSocket实例

Stream Handling

流处理

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

Eden Configuration

Eden配置

typescript
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 */ }
})
typescript
const api = treaty<App>('localhost:3000', {
  fetch: { credentials: 'include' },
  headers: { authorization: 'Bearer token' },
  headers: (path) => ({ /* 动态请求头 */ }),
  onRequest: (path, options) => { /* 修改请求配置 */ },
  onResponse: (response) => { /* 修改响应 */ }
})

Unit Testing with Eden

使用Eden进行单元测试

typescript
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')
typescript
import { treaty } from '@elysiajs/eden'
import { app } from './server'

// 直接传入实例 - 无需网络请求
const api = treaty(app)

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

Testing Patterns

测试模式

Unit Testing with bun:test

使用bun:test进行单元测试

typescript
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' })
  })
})
typescript
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('返回Hello', async () => {
    const res = await app.handle(new Request('http://localhost/hello'))
    expect(await res.text()).toBe('Hello')
  })

  it('创建用户', 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

使用Eden进行测试

typescript
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')
})
typescript
import { treaty } from '@elysiajs/eden'
import { app } from './server'

const api = treaty(app)

it('应通过类型安全创建用户', 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
src/
├── modules/
│   ├── auth/
│   │   ├── index.ts       # 路由定义
│   │   ├── service.ts     # 业务逻辑
│   │   └── model.ts       # TypeBox Schema
│   ├── user/
│   └── product/
├── shared/
│   ├── middleware/
│   └── utils/
├── config/
│   └── env.ts
├── index.ts
└── server.ts

Module Pattern

模块模式

typescript
// 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))
typescript
// 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

生产构建

bash
undefined
bash
undefined

Compile to binary

编译为二进制文件

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

Cluster Mode

集群模式

typescript
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')
}
typescript
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

最佳实践

  1. Always use method chaining - Maintains type inference
  2. Name plugins - Enables deduplication
  3. Use resolve over derive - When validation is needed first
  4. Export type App - For Eden client type safety
  5. Use guards - For shared validation across routes
  6. Local hooks by default - Explicit
    as: 'scoped'
    or
    as: 'global'
  7. Extract services - Outside Elysia for testability
  8. Use status() function - For type-safe status responses
  1. 始终使用方法链 - 保持类型推断能力
  2. 为插件命名 - 启用去重功能
  3. 优先使用resolve而非derive - 当需要先完成验证时
  4. 导出App类型 - 为Eden客户端提供类型安全
  5. 使用守卫 - 为多个路由共享验证逻辑
  6. 默认使用局部钩子 - 显式指定
    as: 'scoped'
    as: 'global'
  7. 提取服务层 - 放在Elysia外部以提升可测试性
  8. 使用status()函数 - 实现类型安全的状态码响应

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.
查看<reference/core-api.md>获取完整API文档。 查看<reference/lifecycle-hooks.md>获取钩子执行细节。 查看<reference/plugins.md>获取所有插件配置。 查看<patterns/authentication.md>获取认证实现方案。 查看<patterns/testing.md>获取测试策略。