Elysia — 长在 Bun 上的极致类型安全 Web 框架
已复核Elysia 是一个默认优先适配 Bun、并用 TypeBox / Standard Schema 同时约束运行时校验与 TypeScript 类型的 Web 框架。日常类比:它像一台出厂就配专属充电桩的车——在 Bun 上走 BunAdapter 和 listen();换到只认 Web 标准的环境,必须改用 fetch 出口,不能假装插座还在。
你写:
import { Elysia, t } from "elysia"
new Elysia() .get("/hi/:name", ({ params }) => `hello ${params.name}`, { params: t.Object({ name: t.String() }), }) .listen(3000)t 从 TypeBox 的 Type 扩出来。同一份 schema 会在注册时编成校验器;过不了的请求在 handler 前变成 ValidationError。
不理解 Elysia 的 adapter / AOT / schema 分层,就解释不了下面几件事:
- 为什么
typeof Bun !== 'undefined'时默认是BunAdapter,否则是WebStandardAdapter - 为什么 WebStandard 上调用
.listen()会直接抛错 - 为什么
ELYSIA_AOT=false后不再走composeGeneralHandler - 为什么旧文把 sucrose 写成 “Bun bundler 把 derive inline 进 handler”
Elysia 的编译链可以拆成五步:
-
选 adapter:构造函数里
config.adapter优先;否则看全局Bun。adapter 负责listen/stop、响应映射,以及是否按 Web 标准暴露fetch。 -
方法链累积类型:
.get()/.post()/.use()/.derive()返回同一个实例家族,把路由、schema、生命周期和推导字段合并进类型参数。 -
schema 编译:路由 hook 里的
body/params/query/headers交给getSchemaValidator。输入可以是 TypeBoxTSchema,也可以是 Standard Schema-like 对象。 -
AOT 或动态 handler:
aot默认 true,除非环境变量ELYSIA_AOT等于字符串'false'。AOT 走composeGeneralHandler;关闭后走createDynamicHandler,路由进router.dynamic。 -
sucrose 只做静态推断:它把 handler 源码拆开,标记这个函数有没有碰
query/body/cookie等字段。这是框架自己的编译辅助,不是 Bun bundler macro。
案例 1:schema 先于 handler
Section titled “案例 1:schema 先于 handler”import { Elysia, t } from "elysia"
new Elysia() .post("/users", ({ body }) => ({ ok: true, who: body.email }), { body: t.Object({ email: t.String({ format: "email" }), age: t.Number({ minimum: 0 }), }), }) .listen(3000)校验失败时固定 ValidationError.status = 422,请求不会进 handler。age: -1 会被 TypeBox 校验拒绝;这是运行时合同,不是只靠 TypeScript。
案例 2:用 .derive() 注入字段,而不是外仓 JWT 插件
Section titled “案例 2:用 .derive() 注入字段,而不是外仓 JWT 插件”import { Elysia, t } from "elysia"
new Elysia() .derive(({ headers }) => ({ requestId: headers["x-request-id"] ?? "missing", })) .get("/whoami", ({ requestId }) => ({ requestId }))固定源码里 .derive() 默认 as: 'local',并作为 transform hook 注册。@elysiajs/jwt 不在本仓,本轮未打开,不能把它的 ctx.jwt 写成核心合同。
案例 3:没有 Bun 时不要调用 listen()
Section titled “案例 3:没有 Bun 时不要调用 listen()”import { Elysia } from "elysia"
const app = new Elysia() .get("/", () => "hi")
export default appWebStandardAdapter.listen 的实现是抛 WebStandard does not support listen, you might want to export default Elysia.fetch instead。Node 或 Edge 上应导出 app.fetch,或显式传入实现了 listen 的 adapter。本仓 adapter 目录只有 bun、web-standard、cloudflare-worker。
-
把
.listen(3000)当成跨运行时 API:它只是this['~adapter'].listen(this)。WebStandard 会抛错。 -
把 sucrose 当成打包器宏:它分析函数字符串并服务 compose;关掉 AOT 后走动态路由,并不是“换到 Node 就失去 inline”。
-
不写 schema 还期待运行时拒绝坏数据:没有 schema 就没有这条校验链;TypeScript 类型也不会凭空出现。
-
把 Eden Treaty 写进本仓合同:端到端客户端是独立包,本轮源码树里没有它。
-
以为默认永远 AOT:
ELYSIA_AOT=false会改走 dynamic handler;生产行为要以启动环境为准。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 新服务可以跑 Bun,并愿意用
t.Object或 Standard Schema 当输入合同 - 前后端同仓 TypeScript,需要路由类型随着方法链增长
- 需要框架在校验失败时给出固定 422,而不是把脏数据交给 handler
不适用:
- 只能在 Node listen,又不想提供自定义 adapter——默认 WebStandard 会拒绝
listen - 需要本轮未核验的 QPS / TechemPower 数字来证明“比 Express 快”
- 团队不接受 TypeBox 报错和巨型方法链类型
固定版本边界
Section titled “固定版本边界”- 本文绑定
elysiajs/elysia@e037eca7...,tag1.4.30、package 与 npmgitHead均为同一提交。 - 默认
aot: env.ELYSIA_AOT !== 'false',nativeStaticResponse: true,encodeSchema: true,normalize: true。 ValidationError.status固定为 422;WebStandard 的listen固定抛错。- 本文未安装依赖、运行
test/或测量吞吐,状态保持UNVERIFIED。
- 默认 adapter 是运行时探测,不是口号——有
Bun才是 Bun-first;没有就只剩 fetch 出口。 - schema 库和框架是叠在一起的——
t基于 TypeBox,但校验入口也承认 Standard Schema。 - AOT 是框架自己生成函数,不是打包器魔法——sucrose / compose 都在本仓。
.use()合并的是另一份 Elysia 或 plugin 函数——类型累积发生在合并之后,不在外仓插件里凭空出现。
- 全局没有
Bun时,默认 adapter 是谁?对它调用.listen(3000)会怎样? ValidationError的默认 HTTP 状态码是多少?sucrose做的是 Bun bundler inline,还是分析 handler 源码里用了哪些 context 字段?
检查点:
WebStandardAdapter。listen抛错,应导出fetch。- 422。
- 后者。它给 compose 提供字段使用推断。
- 官方文档:elysiajs.com
- 固定源码:elysiajs/elysia —— 本文绑定提交
e037eca710e7ad193be09cc6615ab0dbe54af914 - 对照入口:
src/index.ts、src/schema.ts、src/sucrose.ts、src/adapter/web-standard/index.ts - TypeBox:sinclairzx81/typebox
- hono —— 同属 TypeScript Web 框架,但入口是跨运行时
fetch,schema 不是默认主链
- hono —— 同样 Web 标准 + 边缘 runtime,但不绑 Bun,类型推导稍弱
- fastify —— Node 上 schema-first 的老前辈,TypeBox 思路的源头之一
- express —— 反面参照:req/res 弱类型,看完 Elysia 更能感受痛点
- koa —— method chain + 中间件思路的早期代表,Elysia 是它的类型化后继
- nestjs —— 重型企业框架,与 Elysia 形成”DI 重 vs 极简”两极
- trpc —— 端到端类型安全的另一路线(RPC over JSON),与 Eden Treaty 对照
- bun —— Elysia 的”地基”,没它谈不上 Elysia