localhost
GET
处理程序是一个响应每个路由请求的函数。
接受请求信息并向客户端返回响应。
或者,在其他框架中,处理程序也被称为 Controller。
import { Elysia } from 'elysia'
new Elysia()
// 函数 `() => 'hello world'` 是一个处理程序
.get('/', () => 'hello world')
.listen(3000)
处理程序可以是字面值,并且可以内联使用。
import { Elysia, file } from 'elysia'
new Elysia()
.get('/', 'Hello Elysia')
.get('/video', file('kyuukurarin.mp4'))
.listen(3000)
使用内联值总是返回相同的值,这对于优化静态资源(如文件)的性能很有用。
这允许 Elysia 提前编译响应以优化性能。
TIP
提供内联值不是缓存。
静态资源值、标头和状态可以使用生命周期动态修改。
Context 包含每个请求唯一的请求信息,除了 store
(全局可变状态) 外,不会与其他请求共享。
import { Elysia } from 'elysia'
new Elysia()
.get('/', (context) => context.path)
// ^ 这是上下文
Context 只能在路由处理程序中检索。它包括:
一个带有类型收窄的函数,用于返回自定义状态码。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ status }) => status(418, "Kirifuji Nagisa"))
.listen(3000)
GET
推荐使用 never-throw 方法返回 status 而不是抛出异常,因为它:
set 是一个可变属性,用于形成通过 Context.set
访问的响应。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set, status }) => {
set.headers = { 'X-Teapot': 'true' }
return status(418, 'I am a teapot')
})
.listen(3000)
允许我们附加或删除响应标头,表示为对象。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.headers['x-powered-by'] = 'Elysia'
return 'a mimir'
})
.listen(3000)
TIP
Elysia 提供小写的自动补全,以确保大小写敏感性一致,例如使用 set-cookie
而不是 Set-Cookie
。
将请求重定向到另一个资源。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ redirect }) => {
return redirect('https://youtu.be/whpVWVWBW4U?&t=8')
})
.get('/custom-status', ({ redirect }) => {
// 也可以设置自定义状态进行重定向
return redirect('https://youtu.be/whpVWVWBW4U?&t=8', 302)
})
.listen(3000)
使用重定向时,返回值不是必需的,并且将被忽略。因为响应将来自另一个资源。
如果未提供,则设置默认状态码。
推荐在只需要返回特定状态码的插件中使用此功能,同时允许用户返回自定义值。例如,HTTP 201/206 或 403/405 等。
import { Elysia } from 'elysia'
new Elysia()
.onBeforeHandle(({ set }) => {
set.status = 418
return 'Kirifuji Nagisa'
})
.get('/', () => 'hi')
.listen(3000)
与 status
函数不同,set.status
无法推断返回值类型,因此无法检查返回值是否正确类型化为响应架构。
TIP
HTTP 状态指示响应类型。如果路由处理程序成功执行而无错误,Elysia 将返回状态码 200。
也可以使用状态码的通用名称而不是数字来设置状态码。
// @errors 2322
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.status
return 'Kirifuji Nagisa'
})
.listen(3000)
Elysia 提供了一个可变信号,用于与 Cookie 交互。
没有 get/set,您可以直接提取 Cookie 名称并检索或更新其值。
import { Elysia } from 'elysia'
new Elysia()
.get('/set', ({ cookie: { name } }) => {
// 获取
name.value
// 设置
name.value = "New Value"
})
有关更多信息,请参阅 Patterns: Cookie。
将请求重定向到另一个资源。
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ redirect }) => {
return redirect('https://youtu.be/whpVWVWBW4U?&t=8')
})
.get('/custom-status', ({ redirect }) => {
// 也可以设置自定义状态进行重定向
return redirect('https://youtu.be/whpVWVWBW4U?&t=8', 302)
})
.listen(3000)
使用重定向时,返回值不是必需的,并且将被忽略。因为响应将来自另一个资源。
我们可以通过直接从处理程序返回 form
实用工具来返回 FormData
。
import { Elysia, form, file } from 'elysia'
new Elysia()
.get('/', () => form({
name: 'Tea Party',
images: [file('nagi.web'), file('mika.webp')]
}))
.listen(3000)
如果需要返回文件或 multipart 表单数据,这种模式很有用。
或者,您可以直接返回 file
来返回单个文件,而无需使用 form
。
import { Elysia, file } from 'elysia'
new Elysia()
.get('/', file('nagi.web'))
.listen(3000)
通过使用带有 yield
关键字的生成器函数,即开箱即用地返回响应流。
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/ok', function* () {
yield 1
yield 2
yield 3
})
在这个例子中,我们可以使用 yield
关键字来流式传输响应。
Elysia 通过提供 sse
实用函数支持 Server Sent Events。
import { Elysia, sse } from 'elysia'
new Elysia()
.get('/sse', function* () {
yield sse('hello world')
yield sse({
event: 'message',
data: {
message: 'This is a message',
timestamp: new Date().toISOString()
},
})
})
当值被包装在 sse
中时,Elysia 将自动将响应标头设置为 text/event-stream
,并将数据格式化为 SSE 事件。
标头只能在第一个块被 yield 之前设置。
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/ok', function* ({ set }) {
// 这将设置标头
set.headers['x-name'] = 'Elysia'
yield 1
yield 2
// 这将无效果
set.headers['x-id'] = '1'
yield 3
})
一旦第一个块被 yield,Elysia 将向客户端发送标头,因此在第一个块被 yield 后修改标头将无效果。
如果响应在没有 yield 的情况下返回,Elysia 将自动将流转换为正常响应。
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/ok', function* () {
if (Math.random() > 0.5) return 'ok'
yield 1
yield 2
yield 3
})
这允许我们有条件地流式传输响应,如果需要,可以返回正常响应。
在响应流完成之前,如果用户取消请求,Elysia 将自动停止生成器函数。
Eden 将流响应解释为 AsyncGenerator
,允许我们使用 for await
循环消耗流。
import { Elysia } from 'elysia'
import { treaty } from '@elysiajs/eden'
const app = new Elysia()
.get('/ok', function* () {
yield 1
yield 2
yield 3
})
const { data, error } = await treaty(app).ok.get()
if (error) throw error
for await (const chunk of data)
console.log(chunk)
Elysia 建立在 Web 标准 Request 之上,该 Request 在 Node、Bun、Deno、Cloudflare Worker、Vercel Edge Function 等多个运行时之间共享。
import { Elysia } from 'elysia'
new Elysia()
.get('/user-agent', ({ request }) => {
return request.headers.get('user-agent')
})
.listen(3000)
如果需要,这允许您访问低级请求信息。
服务器实例是 Bun 服务器实例,允许我们访问服务器信息,如端口号或请求 IP。
服务器仅在 HTTP 服务器使用 listen
运行时可用。
import { Elysia } from 'elysia'
new Elysia()
.get('/port', ({ server }) => {
return server?.port
})
.listen(3000)
我们可以使用 server.requestIP
方法获取请求 IP。
import { Elysia } from 'elysia'
new Elysia()
.get('/ip', ({ server, request }) => {
return server?.requestIP(request)
})
.listen(3000)
由于 Elysia 只提供基本信息,我们可以自定义 Context 以满足特定需求,例如:
我们可以使用以下 API 扩展 Elysia 的上下文来自定义 Context:
您应该只在以下情况下扩展上下文:
否则,我们推荐将值或函数单独定义,而不是扩展上下文。
TIP
推荐将与请求和响应相关的属性,或经常使用的函数分配给 Context 以实现关注点分离。
State 是整个 Elysia 应用共享的全局可变对象或状态。
一旦调用 state,值将在调用时添加到 store 属性中,并在处理程序中使用。
import { Elysia } from 'elysia'
new Elysia()
.state('version', 1)
.get('/a', ({ store: { version } }) => version)
.get('/b', ({ store }) => store)
.get('/c', () => 'still ok')
.listen(3000)
GET
wrapper
值或类来变异内部状态,请改用 decorate。import { Elysia } from 'elysia'
new Elysia()
// ❌ TypeError: counter 不存在于 store 中
.get('/error', ({ store }) => store.counter)Property 'counter' does not exist on type '{}'. .state('counter', 0)
// ✅ 因为我们在之前分配了 counter,现在可以访问它
.get('/', ({ store }) => store.counter)
GET
TIP
注意,我们不能在使用之前使用状态值。
Elysia 会自动将状态值注册到存储中,而无需显式类型或额外的 TypeScript 泛型。
要变异状态,推荐使用 引用 来变异而不是使用实际值。
在从 JavaScript 访问属性时,如果我们将对象属性的原始值定义为新值,则引用丢失,该值被视为新的单独值。
例如:
const store = {
counter: 0
}
store.counter++
console.log(store.counter) // ✅ 1
我们可以访问和变异属性 store.counter。
然而,如果我们将 counter 定义为新值
const store = {
counter: 0
}
let counter = store.counter
counter++
console.log(store.counter) // ❌ 0
console.log(counter) // ✅ 1
一旦原始值被重新定义为新变量,引用 “链接” 将丢失,导致意外行为。
这可以应用于 store
,因为它是一个全局可变对象。
import { Elysia } from 'elysia'
new Elysia()
.state('counter', 0)
// ✅ 使用引用,值是共享的
.get('/', ({ store }) => store.counter++)
// ❌ 在原始值上创建新变量,链接丢失
.get('/error', ({ store: { counter } }) => counter)
GET
decorate 在 调用时 直接将附加属性分配给 Context。
import { Elysia } from 'elysia'
class Logger {
log(value: string) {
console.log(value)
}
}
new Elysia()
.decorate('logger', new Logger())
// ✅ 从前一行定义
.get('/', ({ logger }) => {
logger.log('hi')
return 'hi'
})
从 Context 中的现有属性检索值并分配新属性。
Derive 在请求发生时 在转换生命周期 分配,允许我们 “derive” (从现有属性创建新属性)。
import { Elysia } from 'elysia'
new Elysia()
.derive(({ headers }) => {
const auth = headers['authorization']
return {
bearer: auth?.startsWith('Bearer ') ? auth.slice(7) : null
}
})
.get('/', ({ bearer }) => bearer)
GET
因为 derive 在新请求启动时被分配,derive 可以访问请求属性,如 headers、query、body,而 store 和 decorate 不能。
类似于 derive,但确保类型完整性。
Resolve 允许我们将新属性分配给上下文。
Resolve 在 beforeHandle 生命周期或 验证后 调用,允许我们安全地 resolve 请求属性。
import { Elysia, t } from 'elysia'
new Elysia()
.guard({
headers: t.Object({
bearer: t.String({
pattern: '^Bearer .+$'
})
})
})
.resolve(({ headers }) => {
return {
bearer: headers.bearer.slice(7)
}
})
.get('/', ({ bearer }) => bearer)
由于 resolve 和 derive 基于 transform 和 beforeHandle 生命周期,我们可以从 resolve 和 derive 返回错误。如果从 derive 返回错误,Elysia 将提前退出并将错误作为响应返回。
import { Elysia } from 'elysia'
new Elysia()
.derive(({ headers, status }) => {
const auth = headers['authorization']
if(!auth) return status(400)
return {
bearer: auth?.startsWith('Bearer ') ? auth.slice(7) : null
}
})
.get('/', ({ bearer }) => bearer)
state、decorate 提供了类似的 API 模式,用于将属性分配给 Context,如下所示:
而 derive 只能与 重映射 一起使用,因为它依赖于现有值。
我们可以使用 state 和 decorate 以键值模式分配值。
import { Elysia } from 'elysia'
class Logger {
log(value: string) {
console.log(value)
}
}
new Elysia()
.state('counter', 0)
.decorate('logger', new Logger())
这种模式对于设置单个属性来说可读性很好。
分配多个属性最好包含在一个对象中进行单一分配。
import { Elysia } from 'elysia'
new Elysia()
.decorate({
logger: new Logger(),
trace: new Trace(),
telemetry: new Telemetry()
})
对象提供了设置多个值的更少重复的 API。
重映射是一种函数重新分配。
允许我们从现有值创建新值,如重命名或移除属性。
通过提供一个函数,并返回一个全新的对象来重新分配值。
import { Elysia } from 'elysia'
new Elysia()
.state('counter', 0)
.state('version', 1)
.state(({ version, ...store }) => ({
...store,
elysiaVersion: 1
}))
// ✅ 从状态重映射创建
.get('/elysia-version', ({ store }) => store.elysiaVersion)
// ❌ 从状态重映射排除
.get('/version', ({ store }) => store.version)Property 'version' does not exist on type '{ elysiaVersion: number; counter: number; }'.
GET
使用状态重映射来从现有值创建新的初始值是一个好主意。
然而,需要注意的是,Elysia 不提供这种方法的响应性,因为重映射仅分配初始值。
TIP
使用重映射,Elysia 将返回的对象视为新属性,移除对象中缺少的任何属性。
为了提供更流畅的体验,一些插件可能有很多属性值,一一重映射可能会很繁琐。
Affix 函数由 prefix 和 suffix 组成,允许我们重映射实例的所有属性。
import { Elysia } from 'elysia'
const setup = new Elysia({ name: 'setup' })
.decorate({
argon: 'a',
boron: 'b',
carbon: 'c'
})
const app = new Elysia()
.use(setup)
.prefix('decorator', 'setup')
.get('/', ({ setupCarbon, ...rest }) => setupCarbon)
GET
允许我们轻松批量重映射插件的属性,防止插件的名称冲突。
默认情况下,affix 将自动处理运行时和类型级代码,将属性重映射为驼峰命名约定。
在某些情况下,我们也可以重映射插件的 all
属性:
import { Elysia } from 'elysia'
const setup = new Elysia({ name: 'setup' })
.decorate({
argon: 'a',
boron: 'b',
carbon: 'c'
})
const app = new Elysia()
.use(setup)
.prefix('all', 'setup')
.get('/', ({ setupCarbon, ...rest }) => setupCarbon)