Skip to content
我们的赞助商

处理程序

处理程序是一个响应每个路由请求的函数。

接受请求信息并向客户端返回响应。

或者,在其他框架中,处理程序也被称为 Controller

typescript
import { Elysia } from 'elysia'

new Elysia()
    // 函数 `() => 'hello world'` 是一个处理程序
    .get('/', () => 'hello world')
    .listen(3000)

处理程序可以是字面值,并且可以内联使用。

typescript
import { Elysia, file } from 'elysia'

new Elysia()
    .get('/', 'Hello Elysia')
    .get('/video', file('kyuukurarin.mp4'))
    .listen(3000)

使用内联值总是返回相同的值,这对于优化静态资源(如文件)的性能很有用。

这允许 Elysia 提前编译响应以优化性能。

TIP

提供内联值不是缓存。

静态资源值、标头和状态可以使用生命周期动态修改。

上下文

Context 包含每个请求唯一的请求信息,除了 store (全局可变状态) 外,不会与其他请求共享。

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
get
('/', (
context
) =>
context
.
path
)
// ^ 这是上下文

Context 只能在路由处理程序中检索。它包括:

属性

  • body - HTTP 消息,表单或文件上传。
  • query - 查询字符串,作为 JavaScript 对象包含用于搜索查询的附加参数。(查询是从路径名后的“?”问号符号后的值中提取的)
  • params - Elysia 的路径参数解析为 JavaScript 对象
  • headers - HTTP 标头,关于请求的附加信息,如 User-Agent、Content-Type、缓存提示。
  • cookie - 用于与 Cookie 交互的全局可变信号存储(包括 get/set)
  • store - Elysia 实例的全局可变存储

实用函数

  • redirect - 用于重定向响应的函数
  • status - 用于返回自定义状态码的函数
  • set - 应用于 Response 的属性:

附加属性

status

一个带有类型收窄的函数,用于返回自定义状态码。

typescript
import { Elysia } from 'elysia'

new Elysia()
    .get('/', ({ status }) => status(418, "Kirifuji Nagisa"))
    .listen(3000)
localhost

GET

推荐使用 never-throw 方法返回 status 而不是抛出异常,因为它:

  • 允许 TypeScript 检查返回值是否正确类型化为响应架构
  • 根据状态码提供类型收窄的自动补全
  • 使用端到端类型安全 (Eden) 进行错误处理类型收窄

Set

set 是一个可变属性,用于形成通过 Context.set 访问的响应。

ts
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
get
('/', ({
set
,
status
}) => {
set
.
headers
= { 'X-Teapot': 'true' }
return
status
(418, 'I am a teapot')
}) .
listen
(3000)

set.headers

允许我们附加或删除响应标头,表示为对象。

typescript
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

redirect Legacy

将请求重定向到另一个资源。

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

使用重定向时,返回值不是必需的,并且将被忽略。因为响应将来自另一个资源。

set.status Legacy

如果未提供,则设置默认状态码。

推荐在只需要返回特定状态码的插件中使用此功能,同时允许用户返回自定义值。例如,HTTP 201/206 或 403/405 等。

typescript
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。

也可以使用状态码的通用名称而不是数字来设置状态码。

typescript
// @errors 2322
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
get
('/', ({
set
}) => {
set
.
status
return 'Kirifuji Nagisa' }) .
listen
(3000)

Elysia 提供了一个可变信号,用于与 Cookie 交互。

没有 get/set,您可以直接提取 Cookie 名称并检索或更新其值。

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
get
('/set', ({
cookie
: {
name
} }) => {
// 获取
name
.
value
// 设置
name
.
value
= "New Value"
})

有关更多信息,请参阅 Patterns: Cookie

重定向

将请求重定向到另一个资源。

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

使用重定向时,返回值不是必需的,并且将被忽略。因为响应将来自另一个资源。

Formdata

我们可以通过直接从处理程序返回 form 实用工具来返回 FormData

typescript
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

typescript
import { Elysia, file } from 'elysia'

new Elysia()
	.get('/', file('nagi.web'))
	.listen(3000)

通过使用带有 yield 关键字的生成器函数,即开箱即用地返回响应流。

typescript
import { Elysia } from 'elysia'

const app = new Elysia()
	.get('/ok', function* () {
		yield 1
		yield 2
		yield 3
	})

在这个例子中,我们可以使用 yield 关键字来流式传输响应。

服务器发送事件 (SSE)

Elysia 通过提供 sse 实用函数支持 Server Sent Events

typescript
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 之前设置。

typescript
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 将自动将流转换为正常响应。

typescript
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

Eden 将流响应解释为 AsyncGenerator,允许我们使用 for await 循环消耗流。

typescript
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 等多个运行时之间共享。

typescript
import { Elysia } from 'elysia'

new Elysia()
	.get('/user-agent', ({ request }) => {
		return request.headers.get('user-agent')
	})
	.listen(3000)

如果需要,这允许您访问低级请求信息。

服务器 仅限 Bun

服务器实例是 Bun 服务器实例,允许我们访问服务器信息,如端口号或请求 IP。

服务器仅在 HTTP 服务器使用 listen 运行时可用。

typescript
import { Elysia } from 'elysia'

new Elysia()
	.get('/port', ({ server }) => {
		return server?.port
	})
	.listen(3000)

请求 IP 仅限 Bun

我们可以使用 server.requestIP 方法获取请求 IP。

typescript
import { Elysia } from 'elysia'

new Elysia()
	.get('/ip', ({ server, request }) => {
		return server?.requestIP(request)
	})
	.listen(3000)

扩展上下文 高级概念

由于 Elysia 只提供基本信息,我们可以自定义 Context 以满足特定需求,例如:

  • 将用户 ID 提取为变量
  • 注入通用模式存储库
  • 添加数据库连接

我们可以使用以下 API 扩展 Elysia 的上下文来自定义 Context:

何时扩展上下文

您应该只在以下情况下扩展上下文:

  • 属性是全局可变状态,并在多个路由中使用 state 共享
  • 属性与请求或响应相关,使用 decorate
  • 属性从现有属性派生,使用 derive / resolve

否则,我们推荐将值或函数单独定义,而不是扩展上下文。

TIP

推荐将与请求和响应相关的属性,或经常使用的函数分配给 Context 以实现关注点分离。

状态

State 是整个 Elysia 应用共享的全局可变对象或状态。

一旦调用 state,值将在调用时添加到 store 属性中,并在处理程序中使用。

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
state
('version', 1)
.
get
('/a', ({
store
: {
version
} }) =>
version
)
.
get
('/b', ({
store
}) =>
store
)
.
get
('/c', () => 'still ok')
.
listen
(3000)
localhost

GET

何时使用

  • 当您需要跨多个路由共享原始可变值时
  • 如果您想要使用非原始值或 wrapper 值或类来变异内部状态,请改用 decorate

关键要点

  • store 是整个 Elysia 应用的单一真相源全局可变对象的表示。
  • state 是一个函数,用于将初始值分配给 store,该值稍后可以被变异。
  • 确保在使用处理程序之前分配值。
typescript
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
)
localhost

GET

TIP

注意,我们不能在使用之前使用状态值。

Elysia 会自动将状态值注册到存储中,而无需显式类型或额外的 TypeScript 泛型。

引用和值 陷阱

要变异状态,推荐使用 引用 来变异而不是使用实际值。

在从 JavaScript 访问属性时,如果我们将对象属性的原始值定义为新值,则引用丢失,该值被视为新的单独值。

例如:

typescript
const store = {
    counter: 0
}

store.counter++
console.log(store.counter) // ✅ 1

我们可以访问和变异属性 store.counter

然而,如果我们将 counter 定义为新值

typescript
const store = {
    counter: 0
}

let counter = store.counter

counter++
console.log(store.counter) // ❌ 0
console.log(counter) // ✅ 1

一旦原始值被重新定义为新变量,引用 “链接” 将丢失,导致意外行为。

这可以应用于 store,因为它是一个全局可变对象。

typescript
import { Elysia } from 'elysia'

new Elysia()
    .state('counter', 0)
    // ✅ 使用引用,值是共享的
    .get('/', ({ store }) => store.counter++)
    // ❌ 在原始值上创建新变量,链接丢失
    .get('/error', ({ store: { counter } }) => counter)
localhost

GET

Decorate

decorate调用时 直接将附加属性分配给 Context

typescript
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
  • 可能包含内部可变状态的非原始值或类
  • 向所有处理程序添加附加函数、单例或不可变属性。

关键要点

  • state 不同,装饰的值 不应 被变异,尽管它是可能的
  • 确保在使用处理程序之前分配值。

Derive

⚠️ Derive 不处理类型完整性,您可能想要改用 resolve

Context 中的现有属性检索值并分配新属性。

Derive 在请求发生时 在转换生命周期 分配,允许我们 “derive” (从现有属性创建新属性)

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
derive
(({
headers
}) => {
const
auth
=
headers
['authorization']
return {
bearer
:
auth
?.
startsWith
('Bearer ') ?
auth
.
slice
(7) : null
} }) .
get
('/', ({
bearer
}) =>
bearer
)
localhost

GET

因为 derive 在新请求启动时被分配,derive 可以访问请求属性,如 headersquerybody,而 storedecorate 不能。

何时使用

  • Context 中的现有属性创建新属性,而无需验证或类型检查
  • 当您需要访问无需验证的请求属性如 headersquerybody

关键要点

  • statedecorate 不同,derive 在新请求启动时分配,而不是在调用时。
  • derive 在转换或验证之前调用,Elysia 无法安全确认请求属性的类型,导致其为 unknown。如果您想要从类型化的请求属性分配新值,您可能想要使用 resolve

Resolve

类似于 derive,但确保类型完整性。

Resolve 允许我们将新属性分配给上下文。

Resolve 在 beforeHandle 生命周期或 验证后 调用,允许我们安全地 resolve 请求属性。

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

何时使用

  • Context 中的现有属性创建新属性,并具有类型完整性(类型检查)
  • 当您需要访问带有验证的请求属性如 headersquerybody

关键要点

  • resolve 在 beforeHandle 或验证后调用。Elysia 可以安全确认请求属性的类型,导致其为 typed

来自 resolve/derive 的错误

由于 resolve 和 derive 基于 transformbeforeHandle 生命周期,我们可以从 resolve 和 derive 返回错误。如果从 derive 返回错误,Elysia 将提前退出并将错误作为响应返回。

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

模式 高级概念

statedecorate 提供了类似的 API 模式,用于将属性分配给 Context,如下所示:

  • 键值
  • 对象
  • 重映射

derive 只能与 重映射 一起使用,因为它依赖于现有值。

键值

我们可以使用 statedecorate 以键值模式分配值。

typescript
import { Elysia } from 'elysia'

class Logger {
    log(value: string) {
        console.log(value)
    }
}

new Elysia()
    .state('counter', 0)
    .decorate('logger', new Logger())

这种模式对于设置单个属性来说可读性很好。

对象

分配多个属性最好包含在一个对象中进行单一分配。

typescript
import { Elysia } from 'elysia'

new Elysia()
    .decorate({
        logger: new Logger(),
        trace: new Trace(),
        telemetry: new Telemetry()
    })

对象提供了设置多个值的更少重复的 API。

重映射

重映射是一种函数重新分配。

允许我们从现有值创建新值,如重命名或移除属性。

通过提供一个函数,并返回一个全新的对象来重新分配值。

typescript
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; }'.
localhost

GET

使用状态重映射来从现有值创建新的初始值是一个好主意。

然而,需要注意的是,Elysia 不提供这种方法的响应性,因为重映射仅分配初始值。

TIP

使用重映射,Elysia 将返回的对象视为新属性,移除对象中缺少的任何属性。

Affix 高级概念

为了提供更流畅的体验,一些插件可能有很多属性值,一一重映射可能会很繁琐。

Affix 函数由 prefixsuffix 组成,允许我们重映射实例的所有属性。

ts
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
)
localhost

GET

允许我们轻松批量重映射插件的属性,防止插件的名称冲突。

默认情况下,affix 将自动处理运行时和类型级代码,将属性重映射为驼峰命名约定。

在某些情况下,我们也可以重映射插件的 all 属性:

ts
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
)