localhost
GET
¥Handler
一个处理程序(handler)是一个响应每个路由请求的函数。
¥A handler is a function that responds to the request for each route.
接受请求信息并返回响应给客户端。
¥Accepting request information and returning a response to the client.
或者,在其他框架中,处理程序也称为控制器。
¥Alternatively, a handler is also known as a Controller in other frameworks.
import { Elysia } from 'elysia'
new Elysia()
// the function `() => 'hello world'` is a handler
.get('/', () => 'hello world')
.listen(3000)
处理程序可以是文字值,并且可以内联。
¥A handler may be a literal value, and can be inlined.
import { Elysia, file } from 'elysia'
new Elysia()
.get('/', 'Hello Elysia')
.get('/video', file('kyuukurarin.mp4'))
.listen(3000)
使用内联值始终返回相同的值,这对于优化文件等静态资源的性能非常有用。
¥Using an inline value always returns the same value which is useful to optimize performance for static resources like files.
这允许 Elysia 提前编译响应以优化性能。
¥This allows Elysia to compile the response ahead of time to optimize performance.
提示
提供内联值并非缓存。
¥Providing an inline value is not a cache.
静态资源值、标头和状态可以使用生命周期进行动态变异。
¥Static resource values, headers and status can be mutated dynamically using lifecycle.
¥Context
Context 包含每个请求独有的请求信息,除 store
(全局可变状态) 外,其他请求信息均不共享。
¥Context contains request information which is unique for each request, and is not shared except for store
(global mutable state).
import { Elysia } from 'elysia'
new Elysia()
.get('/', (context) => context.path)
// ^ This is a context
Context 只能在路由处理程序中检索。它包含:
¥Context can only be retrieved in a route handler. It consists of:
path - 请求的路径名
body - HTTP 消息,表单或文件上传。
query - 查询字符串,以 JavaScript 对象的形式包含用于搜索查询的附加参数。(查询从路径名后从 '?' 问号开始的值中提取)
params - Elysia 的路径参数解析为 JavaScript 对象
headers - HTTP 标头,请求的附加信息,例如 User-Agent、Content-Type、缓存提示。
request - Web 标准请求
redirect - 一个用于重定向响应的函数
store - 一个用于 Elysia 实例的全局可变存储
cookie - 一个用于与 Cookie 交互的全局可变信号存储(包括 get/set 方法)
set - 应用于响应的属性:
status - HTTP 状态,如果未设置,则默认为 200。
headers - 响应头
redirect - 响应作为重定向到的路径
error - 一个用于返回自定义状态码的函数
server - Bun 服务器实例
¥Set
set 是一个可变属性,它形成一个可通过 Context.set
访问的响应。
¥set is a mutable property that form a response accessible via Context.set
.
set.status - 设置自定义状态码
set.headers - 附加自定义标头
set.redirect - 附加重定向
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set, status }) => {
set.headers = { 'X-Teapot': 'true' }
return status(418, 'I am a teapot')
})
.listen(3000)
我们可以使用以下任一方式返回自定义状态码:
¥We can return a custom status code by using either:
状态函数(推荐)
set.status (遗留)
import { Elysia } from 'elysia'
new Elysia()
.get('/error', ({ error }) => error(418, 'I am a teapot'))
.get('/set.status', ({ set }) => {
set.status = 418
return 'I am a teapot'
})
.listen(3000)
¥status function
用于返回响应状态码的专用 status
函数。
¥A dedicated status
function for returning status code with response.
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ status }) => status(418, "Kirifuji Nagisa"))
.listen(3000)
GET
建议在主处理程序中使用 status
,因为它具有更好的推断能力:
¥It's recommended to use status
inside the main handler as it has better inference:
允许 TypeScript 检查返回值是否正确匹配响应模式
基于状态码自动补齐类型缩小
使用端到端类型安全 (Eden) 进行错误处理的类型缩小
如果未提供,则设置默认状态码。
¥Set a default status code if not provided.
建议在只需要返回特定状态码的插件中使用它,同时允许用户返回自定义值。例如,HTTP 201/206 或 403/405 等。
¥It's recommended to use this in a plugin that only needs to return a specific status code while allowing the user to return a custom value. For example, HTTP 201/206 or 403/405, etc.
import { Elysia } from 'elysia'
new Elysia()
.onBeforeHandle(({ set }) => {
set.status = 418
return 'Kirifuji Nagisa'
})
.get('/', () => 'hi')
.listen(3000)
与 status
函数不同,set.status
无法推断返回值的类型,因此无法检查返回值的类型是否与响应模式正确匹配。
¥Unlike status
function, set.status
cannot infer the return value type, therefore it can't check if the return value is correctly type to response schema.
提示
HTTP 状态指示响应类型。如果路由处理程序成功执行且没有错误,Elysia 将返回状态码 200。
¥HTTP Status indicates the type of response. If the route handler is executed successfully without error, Elysia will return the status code 200.
你还可以使用状态代码的通用名称(而不是数字)来设置状态代码。
¥You can also set a status code using the common name of the status code instead of using a number.
// @errors 2322
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.status
return 'Kirifuji Nagisa'
})
.listen(3000)
允许我们附加或删除以对象形式表示的响应标头。
¥Allowing us to append or delete response headers represented as an Object.
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ set }) => {
set.headers['x-powered-by'] = 'Elysia'
return 'a mimir'
})
.listen(3000)
警告
标头名称应小写,以强制 HTTP 标头和自动补齐的大小写一致性,例如,使用 set-cookie
而不是 Set-Cookie
。
¥The names of headers should be lowercase to force case-sensitivity consistency for HTTP headers and auto-completion, eg. use set-cookie
rather than Set-Cookie
.
将请求重定向到其他资源。
¥Redirect a request to another resource.
import { Elysia } from 'elysia'
new Elysia()
.get('/', ({ redirect }) => {
return redirect('https://youtu.be/whpVWVWBW4U?&t=8')
})
.get('/custom-status', ({ redirect }) => {
// You can also set custom status to redirect
return redirect('https://youtu.be/whpVWVWBW4U?&t=8', 302)
})
.listen(3000)
使用重定向时,返回值不是必需的,将被忽略。由于响应将来自其他资源。
¥When using redirect, returned value is not required and will be ignored. As response will be from another resource.
¥Server
可以通过 Context.server
访问服务器实例,并与服务器进行交互。
¥Server instance is accessible via Context.server
to interact with the server.
由于服务器可能运行在不同的环境中(测试),因此可以为空。
¥Server could be nullable as it could be running in a different environment (test).
如果服务器正在使用 Bun 运行(分配),则 server
将可用(非空)。
¥If server is running (allocating) using Bun, server
will be available (not null).
import { Elysia } from 'elysia'
new Elysia()
.get('/port', ({ server }) => {
return server?.port
})
.listen(3000)
¥Request IP
我们可以使用 server.requestIP
方法获取请求 IP
¥We can get request IP by using server.requestIP
method
import { Elysia } from 'elysia'
new Elysia()
.get('/ip', ({ server, request }) => {
return server?.requestIP(request)
})
.listen(3000)
¥Response
Elysia 建立在 Web 标准请求/响应之上。
¥Elysia is built on top of Web Standard Request/Response.
为了符合 Web 标准,Elysia 会将路由处理程序返回的值映射到 响应 中。
¥To comply with the Web Standard, a value returned from route handler will be mapped into a Response by Elysia.
让你专注于业务逻辑而不是样板代码。
¥Letting you focus on business logic rather than boilerplate code.
import { Elysia } from 'elysia'
new Elysia()
// Equivalent to "new Response('hi')"
.get('/', () => 'hi')
.listen(3000)
如果你更喜欢显式的 Response 类,Elysia 也会自动处理。
¥If you prefer an explicit Response class, Elysia also handles that automatically.
import { Elysia } from 'elysia'
new Elysia()
.get('/', () => new Response('hi'))
.listen(3000)
提示
使用原始值或 Response
的性能几乎相同(+- 0.1%),因此无论性能如何,都可以选择你喜欢的一种。
¥Using a primitive value or Response
has near identical performance (+- 0.1%), so pick the one you prefer, regardless of performance.
¥Formdata
我们可以直接从处理程序返回 form
实用程序来返回 FormData
。
¥We may return a FormData
by using returning form
utility directly from the handler.
import { Elysia, form, file } from 'elysia'
new Elysia()
.get('/', () => form({
name: 'Tea Party',
images: [file('nagi.web'), file('mika.webp')]
}))
.listen(3000)
即使需要返回文件或多部分表单数据,此模式也非常有用。
¥This pattern is useful if even need to return a file or multipart form data.
¥Return a single file
或者,你可以直接返回 file
而不返回 form
,从而返回单个文件。
¥Or alternatively, you can return a single file by returning file
directly without form
.
import { Elysia, file } from 'elysia'
new Elysia()
.get('/', file('nagi.web'))
.listen(3000)
¥Handle
由于 Elysia 建立在 Web 标准请求之上,我们可以使用 Elysia.handle
以编程方式对其进行测试。
¥As Elysia is built on top of Web Standard Request, we can programmatically test it using Elysia.handle
.
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/', () => 'hello')
.post('/hi', () => 'hi')
.listen(3000)
app.handle(new Request('http://localhost/')).then(console.log)
Elysia.handle 是一个用于处理发送到服务器的实际请求的函数。
¥Elysia.handle is a function to process an actual request sent to the server.
提示
与单元测试的模拟不同,你可以期望它的行为类似于发送到服务器的实际请求。
¥Unlike unit test's mock, you can expect it to behave like an actual request sent to the server.
但它对于模拟或创建单元测试也很有用。
¥But also useful for simulating or creating unit tests.
要使用带有 yield
关键字的生成器函数返回开箱即用的响应流。
¥To return a response streaming out of the box by using a generator function with yield
keyword.
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/ok', function* () {
yield 1
yield 2
yield 3
})
在这个例子中,我们可以使用 yield
关键字流式传输响应。
¥This this example, we may stream a response by using yield
keyword.
¥Server Sent Events (SSE)
Elysia 通过提供 sse
实用函数来支持 服务器发送事件。
¥Elysia supports Server Sent Events by providing a sse
utility function.
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 事件。
¥When a value is wrapped in sse
, Elysia will automatically set the response headers to text/event-stream
and format the data as an SSE event.
¥Set headers
Elysia 将延迟返回响应头,直到生成第一个块。
¥Elysia will defers returning response headers until the first chunk is yielded.
这使我们能够在响应流式传输之前设置标头。
¥This allows us to set headers before the response is streamed.
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/ok', function* ({ set }) {
// This will set headers
set.headers['x-name'] = 'Elysia'
yield 1
yield 2
// This will do nothing
set.headers['x-id'] = '1'
yield 3
})
生成第一个块后,Elysia 将在同一响应中发送标头和第一个块。
¥Once the first chunk is yielded, Elysia will send the headers and the first chunk in the same response.
在生成第一个块后设置标头将不起作用。
¥Setting headers after the first chunk is yielded will do nothing.
¥Conditional Stream
如果响应没有返回 yield,Elysia 会自动将流转换为正常响应。
¥If the response is returned without yield, Elysia will automatically convert stream to normal response instead.
import { Elysia } from 'elysia'
const app = new Elysia()
.get('/ok', function* () {
if (Math.random() > 0.5) return 'ok'
yield 1
yield 2
yield 3
})
这使我们能够有条件地流式传输响应,或在必要时返回正常响应。
¥This allows us to conditionally stream a response or return a normal response if necessary.
¥Abort
在流式传输响应时,请求通常在响应完全流式传输之前就被取消,这是很常见的情况。
¥While streaming a response, it's common that request may be cancelled before the response is fully streamed.
当请求取消时,Elysia 将自动停止生成器函数。
¥Elysia will automatically stop the generator function when the request is cancelled.
Eden 会将流响应解释为 AsyncGenerator
,从而允许我们使用 for await
循环来消费流。
¥Eden will interpret a stream response as AsyncGenerator
allowing us to use for await
loop to consume the stream.
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)
¥Extending context
由于 Elysia 仅提供基本信息,我们可以根据具体需求自定义 Context,例如:
¥As Elysia only provides essential information, we can customize Context for our specific need for instance:
提取用户 ID 作为变量
注入通用模式存储库
添加数据库连接
我们可以使用以下 API 扩展 Elysia 的上下文以自定义上下文:
¥We may extend Elysia's context by using the following APIs to customize the Context:
¥When to extend context
你应该只在以下情况下扩展上下文:
¥You should only extend context when:
否则,我们建议单独定义一个值或函数,而不是扩展上下文。
¥Otherwise, we recommend defining a value or function separately than extending the context.
提示
建议将与请求和响应相关的属性或常用函数分配给 Context,以便分离关注点。
¥It's recommended to assign properties related to request and response, or frequently used functions to Context for separation of concerns.
¥State
状态是 Elysia 应用之间共享的全局可变对象或状态。
¥State is a global mutable object or state shared across the Elysia app.
调用 state 后,value 将在调用时添加到 store 属性中,并可在处理程序中使用。
¥Once state is called, value will be added to store property once at call time, and can be used in handler.
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
¥When to use
当你需要在多个路由之间共享原始可变值时
如果你想使用非原始类型或 wrapper
值或类来改变内部状态,请改用 decorate。
¥Key takeaway
store 是整个 Elysia 应用的单一真实来源全局可变对象的表示。
state 是一个用于分配初始值进行存储的函数,该值稍后可以进行修改。
确保在处理程序中使用它之前先赋值。
import { Elysia } from 'elysia'
new Elysia()
// ❌ TypeError: counter doesn't exist in store
.get('/error', ({ store }) => store.counter)Property 'counter' does not exist on type '{}'. .state('counter', 0)
// ✅ Because we assigned a counter before, we can now access it
.get('/', ({ store }) => store.counter)
GET
提示
请注意,我们不能在赋值之前使用状态值。
¥Beware that we cannot use a state value before assign.
Elysia 自动将状态值注册到存储中,无需显式指定类型或额外的 TypeScript 泛型。
¥Elysia registers state values into the store automatically without explicit type or additional TypeScript generic needed.
¥Decorate
decorate 在调用时直接为 Context 分配一个附加属性。
¥decorate assigns an additional property to Context directly at call time.
import { Elysia } from 'elysia'
class Logger {
log(value: string) {
console.log(value)
}
}
new Elysia()
.decorate('logger', new Logger())
// ✅ defined from the previous line
.get('/', ({ logger }) => {
logger.log('hi')
return 'hi'
})
¥When to use
Context 的常量或只读值对象
可能包含内部可变状态的非原始值或类
为所有处理程序添加额外的函数、单例或不可变属性。
¥Key takeaway
与 state 不同,修饰的值不应该被修改,尽管这是可能的。
确保在处理程序中使用它之前先赋值。
¥Derive
从 Context 中的现有属性中检索值并分配新属性。
¥Retrieve values from existing properties in Context and assign new properties.
在转换生命周期中发生请求时,Derive 会进行赋值,从而允许我们 "derive" (从现有属性创建新属性)。
¥Derive assigns when request happens at transform lifecycle allowing us to "derive" (create new properties from existing properties).
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 则不能。
¥Because derive is assigned once a new request starts, derive can access request properties like headers, query, body where store, and decorate can't.
¥When to use
根据 Context 中现有属性创建一个新属性,无需验证或类型检查
当你需要访问请求属性(例如 headers、query、body)而不进行验证时
¥Key takeaway
与 state 和 decorate 不同,derive 不是在调用时赋值,而是在新请求启动时赋值。
derive 在转换时调用,或者在验证发生之前,Elysia 无法安全地确认请求属性的类型,导致结果为未知。如果你想从类型化的请求属性中分配新值,你可能需要改用 resolve。
¥Resolve
与 derive 相同,resolve 允许我们为 context 分配新属性。
¥Same as derive, resolve allow us to assign a new property to context.
Resolve 在 beforeHandle 生命周期或验证之后被调用,这使我们能够安全地获取请求属性。
¥Resolve is called at beforeHandle lifecycle or after validation, allowing us to derive request properties safely.
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)
¥When to use
根据 Context 中现有属性创建一个新属性,并保证类型完整性(已检查类型)
当你需要访问请求属性(例如 headers、query、body)并进行验证时
¥Key takeaway
¥Error from resolve/derive
由于 resolve 和 derive 基于 transform 和 beforeHandle 生命周期,因此我们可以从 resolve 和 derive 中返回错误。如果 derive 返回错误,Elysia 将提前退出并将错误作为响应返回。
¥As resolve and derive is based on transform and beforeHandle lifecycle, we can return an error from resolve and derive. If error is returned from derive, Elysia will return early exit and return the error as response.
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)
¥Pattern
state、decorate 提供了类似的 API 模式,用于将属性赋值给 Context,如下所示:
¥state, decorate offers a similar APIs pattern for assigning property to Context as the following:
key-value
object
remap
其中 derive 只能与 remap 一起使用,因为它依赖于现有值。
¥Where derive can be only used with remap because it depends on existing value.
我们可以使用 state 和 decorate 来通过键值模式赋值。
¥We can use state, and decorate to assign a value using a key-value pattern.
import { Elysia } from 'elysia'
class Logger {
log(value: string) {
console.log(value)
}
}
new Elysia()
.state('counter', 0)
.decorate('logger', new Logger())
此模式非常适合于设置单个属性的可读性。
¥This pattern is great for readability for setting a single property.
¥Object
最好将多个属性的赋值包含在一个对象中,以便进行单次赋值。
¥Assigning multiple properties is better contained in an object for a single assignment.
import { Elysia } from 'elysia'
new Elysia()
.decorate({
logger: new Logger(),
trace: new Trace(),
telemetry: new Telemetry()
})
对象提供了一个重复性更低的 API 来设置多个值。
¥The object offers a less repetitive API for setting multiple values.
¥Remap
Remap 是一种函数重新赋值。
¥Remap is a function reassignment.
允许我们从现有值创建新值,例如重命名或删除属性。
¥Allowing us to create a new value from existing value like renaming or removing a property.
通过提供一个函数,并返回一个全新的对象来重新赋值。
¥By providing a function, and returning an entirely new object to reassign the value.
import { Elysia } from 'elysia'
new Elysia()
.state('counter', 0)
.state('version', 1)
.state(({ version, ...store }) => ({
...store,
elysiaVersion: 1
}))
// ✅ Create from state remap
.get('/elysia-version', ({ store }) => store.elysiaVersion)
// ❌ Excluded from state remap
.get('/version', ({ store }) => store.version)Property 'version' does not exist on type '{ elysiaVersion: number; counter: number; }'.
GET
使用状态重映射从现有值创建新的初始值是个好主意。
¥It's a good idea to use state remap to create a new initial value from the existing value.
然而,需要注意的是,Elysia 不提供这种方法的响应性,因为 remap 只会分配一个初始值。
¥However, it's important to note that Elysia doesn't offer reactivity from this approach, as remap only assigns an initial value.
提示
使用 remap,Elysia 会将返回的对象视为新属性,并删除对象中缺少的任何属性。
¥Using remap, Elysia will treat a returned object as a new property, removing any property that is missing from the object.
¥Affix
为了提供更流畅的体验,某些插件可能包含大量属性值,逐一重新映射可能会很麻烦。
¥To provide a smoother experience, some plugins might have a lot of property value which can be overwhelming to remap one-by-one.
Affix 函数由前缀和后缀组成,允许我们重新映射实例的所有属性。
¥The Affix function which consists of prefix and suffix, allowing us to remap all property of an instance.
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
允许我们轻松地批量重新映射插件的属性,避免插件的名称冲突。
¥Allowing us to bulk remap a property of the plugin effortlessly, preventing the name collision of the plugin.
默认情况下,affix 将自动处理运行时和类型级代码,并将属性重新映射到驼峰命名规范。
¥By default, affix will handle both runtime, type-level code automatically, remapping the property to camelCase as naming convention.
在某些情况下,我们还可以重新映射插件的 all
属性:
¥In some condition, we can also remap all
property of the plugin:
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)
¥Reference and value
要更改状态,建议使用引用进行更改,而不是使用实际值。
¥To mutate the state, it's recommended to use reference to mutate rather than using an actual value.
从 JavaScript 访问属性时,如果我们将对象属性中的原始值定义为新值,则引用将丢失,该值将被视为新的独立值。
¥When accessing the property from JavaScript, if we define a primitive value from an object property as a new value, the reference is lost, the value is treated as new separate value instead.
例如:
¥For example:
const store = {
counter: 0
}
store.counter++
console.log(store.counter) // ✅ 1
我们可以使用 store.counter 来访问和修改属性。
¥We can use store.counter to access and mutate the property.
但是,如果我们将计数器定义为新值
¥However, if we define a counter as a new value
const store = {
counter: 0
}
let counter = store.counter
counter++
console.log(store.counter) // ❌ 0
console.log(counter) // ✅ 1
一旦将原始值重新定义为新变量,引用 "link" 将会丢失,从而导致意外行为。
¥Once a primitive value is redefined as a new variable, the reference "link" will be missing, causing unexpected behavior.
这可以应用于 store
,因为它是一个全局可变对象。
¥This can apply to store
, as it's a global mutable object instead.
import { Elysia } from 'elysia'
new Elysia()
.state('counter', 0)
// ✅ Using reference, value is shared
.get('/', ({ store }) => store.counter++)
// ❌ Creating a new variable on primitive value, the link is lost
.get('/error', ({ store: { counter } }) => counter)
GET