Skip to content

Eden 安装

¥Eden Installation

首先在前端安装 Eden:

¥Start by installing Eden on your frontend:

bash
bun add @elysiajs/eden
bun add -d elysia

提示

Eden 需要 Elysia 来推断工具类型。

¥Eden needs Elysia to infer utilities type.

确保在服务器上安装与版本匹配的 Elysia。

¥Make sure to install Elysia with the version matching on the server.

首先,导出你现有的 Elysia 服务器类型:

¥First, export your existing Elysia server type:

typescript
// server.ts
import { Elysia, t } from 'elysia'

const app = new Elysia()
    .get('/', () => 'Hi Elysia')
    .get('/id/:id', ({ params: { id } }) => id)
    .post('/mirror', ({ body }) => body, {
        body: t.Object({
            id: t.Number(),
            name: t.String()
        })
    })
    .listen(3000)

export type App = typeof app 

然后在客户端使用 Elysia API:

¥Then consume the Elysia API on client side:

typescript
// client.ts
import { 
treaty
} from '@elysiajs/eden'
import type {
App
} from './server'
const
client
=
treaty
<
App
>('localhost:3000')
// response: Hi Elysia const {
data
:
index
} = await
client
.
get
()
// response: 1895 const {
data
:
id
} = await
client
.
id
({
id
: 1895 }).
get
()
// response: { id: 1895, name: 'Skadi' } const {
data
:
nendoroid
} = await
client
.
mirror
.
post
({
id
: 1895,
name
: 'Skadi'
})
client
.

疑难解答

¥Gotcha

有时,Eden 可能无法正确从 Elysia 推断类型,以下是修复 Eden 类型推断的最常见解决方法。

¥Sometimes, Eden may not infer types from Elysia correctly, the following are the most common workarounds to fix Eden type inference.

类型严格

¥Type Strict

确保在 tsconfig.json 中启用严格模式

¥Make sure to enable strict mode in tsconfig.json

json
{
  "compilerOptions": {
    "strict": true
  }
}

Elysia 版本不匹配

¥Unmatch Elysia version

Eden 依赖于 Elysia 类来导入 Elysia 实例并正确推断类型。

¥Eden depends on Elysia class to import Elysia instance and infer types correctly.

确保客户端和服务器都具有匹配的 Elysia 版本。

¥Make sure that both client and server have the matching Elysia version.

你可以使用 npm why 命令进行检查:

¥You can check it with npm why command:

bash
npm why elysia

输出应该只在顶层包含一个 Elysia 版本:

¥And output should contain only one elysia version on top-level:

elysia@1.1.12
node_modules/elysia
  elysia@"1.1.25" from the root project
  peer elysia@">= 1.1.0" from @elysiajs/html@1.1.0
  node_modules/@elysiajs/html
    dev @elysiajs/html@"1.1.1" from the root project
  peer elysia@">= 1.1.0" from @elysiajs/opentelemetry@1.1.2
  node_modules/@elysiajs/opentelemetry
    dev @elysiajs/opentelemetry@"1.1.7" from the root project
  peer elysia@">= 1.1.0" from @elysiajs/swagger@1.1.0
  node_modules/@elysiajs/swagger
    dev @elysiajs/swagger@"1.1.6" from the root project
  peer elysia@">= 1.1.0" from @elysiajs/eden@1.1.2
  node_modules/@elysiajs/eden
    dev @elysiajs/eden@"1.1.3" from the root project

TypeScript 版本

¥TypeScript version

Elysia 使用 TypeScript 的新功能和语法,以最高效的方式推断类型。Const Generic 和 Template Literal 等功能被广泛使用。

¥Elysia uses newer features and syntax of TypeScript to infer types in the most performant way. Features like Const Generic and Template Literal are heavily used.

如果你的客户端 TypeScript 版本 >= 5.0,请确保其最低版本为 TypeScript

¥Make sure your client has a minimum TypeScript version if >= 5.0

方法链

¥Method Chaining

要使 Eden 正常工作,Elysia 必须使用方法链。

¥To make Eden work, Elysia must use method chaining

Elysia 的类型系统非常复杂,方法通常会为实例引入新的类型。

¥Elysia's type system is complex, methods usually introduce a new type to the instance.

使用方法链将有助于节省新的类型引用。

¥Using method chaining will help save that new type reference.

例如:

¥For example:

typescript
import { 
Elysia
} from 'elysia'
new
Elysia
()
.
state
('build', 1)
// Store is strictly typed .
get
('/', ({
store
: {
build
} }) =>
build
)
.
listen
(3000)

使用此代码,state 现在返回一个新的 ElysiaInstance 类型,将 build 引入到 store 中以替换当前的类型。

¥Using this, state now returns a new ElysiaInstance type, introducing build into store replacing the current one.

如果没有方法链,Elysia 在引入新类型时不会保存,从而导致无法进行类型推断。

¥Without method chaining, Elysia doesn't save the new type when introduced, leading to no type inference.

typescript
import { 
Elysia
} from 'elysia'
const
app
= new
Elysia
()
app
.
state
('build', 1)
app
.
get
('/', ({
store
: { build } }) =>
build
)
Property 'build' does not exist on type '{}'.
app
.
listen
(3000)

类型定义

¥Type Definitions

如果你正在使用 Bun 的特定功能(例如 Bun.file 或类似的 API)并从处理程序返回它,则可能还需要将 Bun 类型定义安装到客户端。

¥If you are using a Bun specific feature, like Bun.file or similar API and return it from a handler, you may need to install Bun type definitions to the client as well.

bash
bun add -d @types/bun

路径别名 (monorepo)

¥Path alias (monorepo)

如果你在 monorepo 中使用路径别名,请确保前端能够解析与后端相同的路径。

¥If you are using path alias in your monorepo, make sure that frontend is able to resolve the path as same as backend.

提示

在 monorepo 中设置路径别名有点棘手,你可以 fork 我们的示例模板:Kozeki 模板 并根据你的需求进行修改。

¥Setting up path alias in monorepo is a bit tricky, you can fork our example template: Kozeki Template and modify it to your needs.

例如,如果你在 tsconfig.json 中为后端指定了以下路径别名:

¥For example, if you have the following path alias for your backend in tsconfig.json:

json
{
  "compilerOptions": {
  	"baseUrl": ".",
	"paths": {
	  "@/*": ["./src/*"]
	}
  }
}

你的后端代码如下所示:

¥And your backend code is like this:

typescript
import { Elysia } from 'elysia'
import { a, b } from '@/controllers'

const app = new Elysia()
	.use(a)
	.use(b)
	.listen(3000)

export type app = typeof app

你必须确保你的前端代码能够解析相同的路径别名。否则,类型推断将被解析为 any。

¥You must make sure that your frontend code is able to resolve the same path alias. Otherwise, type inference will be resolved as any.

typescript
import { treaty } from '@elysiajs/eden'
import type { app } from '@/index'

const client = treaty<app>('localhost:3000')

// This should be able to resolve the same module both frontend and backend, and not `any`
import { a, b } from '@/controllers'

为了解决这个问题,你必须确保路径别名在前端和后端都解析为同一个文件。

¥To fix this, you must make sure that path alias is resolved to the same file in both frontend and backend.

因此,你必须将 tsconfig.json 中的路径别名更改为:

¥So, you must change the path alias in tsconfig.json to:

json
{
  "compilerOptions": {
  	"baseUrl": ".",
	"paths": {
	  "@/*": ["../apps/backend/src/*"]
	}
  }
}

如果配置正确,你应该能够在前端和后端解析相同的模块。

¥If configured correctly, you should be able to resolve the same module in both frontend and backend.

typescript
// This should be able to resolve the same module both frontend and backend, and not `any`
import { a, b } from '@/controllers'

命名空间

¥Namespace

我们建议为 monorepo 中的每个模块添加命名空间前缀,以避免可能发生的任何混淆和冲突。

¥We recommended adding a namespace prefix for each module in your monorepo to avoid any confusion and conflict that may happen.

json
{
  "compilerOptions": {
  	"baseUrl": ".",
	"paths": {
	  "@frontend/*": ["./apps/frontend/src/*"],
	  "@backend/*": ["./apps/backend/src/*"]
	}
  }
}

然后,你可以像这样导入模块:

¥Then, you can import the module like this:

typescript
// Should work in both frontend and backend and not return `any`
import { a, b } from '@backend/controllers'

我们建议创建一个单独的 tsconfig.json 文件,将 baseUrl 定义为代码库的根目录,根据模块位置提供路径,并为每个继承了根目录 tsconfig.json 文件(该目录具有路径别名)的模块创建一个 tsconfig.json 文件。

¥We recommend creating a single tsconfig.json that defines a baseUrl as the root of your repo, provide a path according to the module location, and create a tsconfig.json for each module that inherits the root tsconfig.json which has the path alias.

你可以在此 路径别名示例代码库Kozeki 模板 中找到一个可用的示例。

¥You may find a working example of in this path alias example repo or Kozeki Template.