将 tRPC 服务器集成到 Bun 中与 Elysia
最近,tRPC 因其端到端类型安全的方法而成为网络开发的热门选择,它通过模糊前后端之间的界限,自动推断后端的类型,从而加快了开发速度。
帮助开发者更快速、更安全地编写代码,快速发现数据结构迁移时出现的问题,并消除在前端重新创建类型的冗余步骤。
但在扩展 tRPC 时,我们可以做得更多。
Elysia
Elysia 是一个为 Bun 优化的网络框架,受到包括 tRPC 在内的许多框架的启发。Elysia 默认支持端到端的类型安全,但不同于 tRPC,Elysia 使用许多人已经熟知的类似 Express 的语法,从而消除了 tRPC 的学习曲线。
由于 Bun 是 Elysia 的运行时,Elysia 服务器的速度和吞吐量都非常快,甚至在镜像 JSON 主体时超过了 Express 的 21 倍和 Fastify 的 12 倍(请参见基准测试)。
将现有的 tRPC 服务器结合到 Elysia 中一直是 Elysia 从开始时的首要目标之一。
你可能想要从 tRPC 切换到 Bun 的原因包括:
- 速度显著更快,甚至超过许多在 Nodejs 中运行的流行网络框架,而无需更改一行代码。
- 将 tRPC 与 RESTful 或 GraphQL 扩展,两者可以在同一服务器中共存。
- Elysia 具有像 tRPC 一样的端到端类型安全,但对于大多数开发者几乎没有学习曲线。
- 使用 Elysia 是实验和投资 Bun 运行时的良好起点。
创建 Elysia 服务器
要开始,让我们创建一个新的 Elysia 服务器,确保首先安装了 Bun,然后运行以下命令来搭建 Elysia 项目。
bun create elysia elysia-trpc && cd elysia-trpc && bun add elysia
TIP
有时 Bun 无法正确解析最新的字段,因此我们使用 bun add elysia
来指定 Elysia 的最新版本。
这将创建一个名为 "elysia-trpc" 的文件夹,并预先配置好 Elysia。
让我们通过运行 dev 命令启动开发服务器:
bun run dev
该命令应在端口 :3000 上启动开发服务器。
Elysia tRPC 插件
基于 tRPC Web 标准适配器,Elysia 有一个插件,用于将现有的 tRPC 服务器集成到 Elysia 中。
bun add @trpc/server zod @elysiajs/trpc @elysiajs/cors
假设这是一个现有的 tRPC 服务器:
import { initTRPC } from '@trpc/server'
import { observable } from '@trpc/server/observable'
import { z } from 'zod'
const t = initTRPC.create()
export const router = t.router({
mirror: t.procedure.input(z.string()).query(({ input }) => input),
})
export type Router = typeof router
通常我们只需要导出路由器的类型,但要将 tRPC 集成到 Elysia 中,我们还需要导出路由器的实例。
然后在 Elysia 服务器中,我们导入路由器,并使用 .use(trpc)
注册 tRPC 路由器。
import { Elysia } from 'elysia'
import { cors } from '@elysiajs/cors'
import { trpc } from '@elysiajs/trpc'
import { router } from './trpc'
const app = new Elysia()
.use(cors())
.get('/', () => 'Hello Elysia')
.use(
trpc(router)
)
.listen(3000)
console.log(`🦊 Elysia 正在运行于 ${app.server?.hostname}:${app.server?.port}`)
就这样!🎉
这就是将 tRPC 集成到 Elysia 中的所有步骤,使 tRPC 在 Bun 上运行。
tRPC 配置和上下文
要创建上下文,trpc
可以接受第二个参数,这与 createHTTPServer
配置 tRPC 的方式相同。
例如,将 createContext
添加到 tRPC 服务器中:
// trpc.ts
import { initTRPC } from '@trpc/server'
import { observable } from '@trpc/server/observable'
import type { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch'
import { z } from 'zod'
export const createContext = async (opts: FetchCreateContextFnOptions) => {
return {
name: 'elysia'
}
}
const t = initTRPC.context<Awaited<ReturnType<typeof createContext>>>().create()
export const router = t.router({
mirror: t.procedure.input(z.string()).query(({ input }) => input),
})
export type Router = typeof router
在 Elysia 服务器中:
import { Elysia } from 'elysia'
import { cors } from '@elysiajs/cors'
import '@elysiajs/trpc'
import { router, createContext } from './trpc'
const app = new Elysia()
.use(cors())
.get('/', () => 'Hello Elysia')
.use(
trpc(router, {
createContext
})
)
.listen(3000)
console.log(`🦊 Elysia 正在运行于 ${app.server?.hostname}:${app.server?.port}`)
我们还可以使用 endpoint
指定 tRPC 的自定义端点:
import { Elysia } from 'elysia'
import { cors } from '@elysiajs/cors'
import { trpc } from '@elysiajs/trpc'
import { router, createContext } from './trpc'
const app = new Elysia()
.use(cors())
.get('/', () => 'Hello Elysia')
.use(
trpc(router, {
createContext,
endpoint: '/v2/trpc'
})
)
.listen(3000)
console.log(`🦊 Elysia 正在运行于 ${app.server?.hostname}:${app.server?.port}`)
订阅
默认情况下,tRPC 使用 WebSocketServer 来支持 subscription
,但不幸的是,因为 Bun 0.5.4 尚不支持 WebSocketServer,所以我们无法直接使用 WebSocket Server。
然而,Bun 确实支持 Web Socket,使用 Bun.serve
,并且由于 Elysia tRPC 插件已将 tRPC 的 Web Socket 的所有用法接线到 Bun.serve
,您可以直接使用 Elysia Web Socket 插件的 tRPC subscription
:
首先安装 Web Socket 插件:
bun add @elysiajs/websocket
然后在 tRPC 服务器内部:
import { initTRPC } from '@trpc/server'
import { observable } from '@trpc/server/observable'
import type { FetchCreateContextFnOptions } from '@trpc/server/adapters/fetch'
import { EventEmitter } from 'stream'
import { zod } from 'zod'
export const createContext = async (opts: FetchCreateContextFnOptions) => {
return {
name: 'elysia'
}
}
const t = initTRPC.context<Awaited<ReturnType<typeof createContext>>>().create()
const ee = new EventEmitter()
export const router = t.router({
mirror: t.procedure.input(z.string()).query(({ input }) => {
ee.emit('listen', input)
return input
}),
listen: t.procedure.subscription(() =>
observable<string>((emit) => {
ee.on('listen', (input) => {
emit.next(input)
})
})
)
})
export type Router = typeof router
然后我们注册:
import { Elysia, ws } from 'elysia'
import { cors } from '@elysiajs/cors'
import '@elysiajs/trpc'
import { router, createContext } from './trpc'
const app = new Elysia()
.use(cors())
.use(ws())
.get('/', () => 'Hello Elysia')
.trpc(router, {
createContext
})
.listen(3000)
console.log(`🦊 Elysia 正在运行于 ${app.server?.hostname}:${app.server?.port}`)
这就是将现有完整功能的 tRPC 服务器集成到 Elysia 服务器的所有步骤,从而使 tRPC 在 Bun 上运行 🥳。
当您需要同时使用 tRPC 和 REST API 时,Elysia 非常出色,因为它们可以在一个服务器中共存。
奖励:使用 Eden 的类型安全 Elysia
由于 Elysia 受到 tRPC 的启发,Elysia 也默认支持像 tRPC 一样的端到端类型安全,使用 "Eden"。
这意味着您可以使用类似 Express 的语法创建完全类型支持的 RESTful API,就像 tRPC 一样。
要开始,让我们导出应用程序类型。
import { Elysia, ws } from 'elysia'
import { cors } from '@elysiajs/cors'
import { trpc } from '@elysiajs/trpc'
import { router, createContext } from './trpc'
const app = new Elysia()
.use(cors())
.use(ws())
.get('/', () => 'Hello Elysia')
.use(
trpc(router, {
createContext
})
)
.listen(3000)
export type App = typeof app
console.log(`🦊 Elysia 正在运行于 ${app.server?.hostname}:${app.server?.port}`)
在客户端:
bun add @elysia/eden && bun add -d elysia
在代码中:
import { edenTreaty } from '@elysiajs/eden'
import type { App } from '../server'
// 这现在具有来自服务器的所有类型推断
const app = edenTreaty<App>('http://localhost:3000')
// data 将具有值 'Hello Elysia',并且类型为 'string'
const data = await app.index.get()
当您希望获得像 tRPC 一样的端到端类型安全,但需要支持更标准的模式(如 REST),同时仍然要支持 tRPC 或需要从一个迁移到另一个时,Elysia 是一个不错的选择。
奖励:Elysia 的额外提示
你可以做的另一个附加事情是,Elysia 不仅支持 tRPC 和端到端类型安全,还有各种为 Bun 配置的基本插件支持。
例如,您可以仅用一行代码使用 Swagger 插件 生成文档。
import { Elysia, t } from 'elysia'
import { swagger } from '@elysiajs/swagger'
const app = new Elysia()
.use(swagger())
.setModel({
sign: t.Object({
username: t.String(),
password: t.String()
})
})
.get('/', () => 'Hello Elysia')
.post('/typed-body', ({ body }) => body, {
schema: {
body: 'sign',
response: 'sign'
}
})
.listen(3000)
export type App = typeof app
console.log(`🦊 Elysia 正在运行于 ${app.server?.hostname}:${app.server?.port}`)
或者当您想在 Bun 上使用 GraphQL Apollo 时。
import { Elysia } from 'elysia'
import { apollo, gql } from '@elysiajs/apollo'
const app = new Elysia()
.use(
apollo({
typeDefs: gql` // [!code ++]
type Book { // [!code ++]
title: String // [!code ++]
author: String // [!code ++]
} // [!code ++]
type Query { // [!code ++]
books: [Book] // [!code ++]
} // [!code ++]
`,
resolvers: {
Query: {
books: () => {
return [
{
title: 'Elysia',
author: 'saltyAom'
}
]
}
}
}
})
)
.get('/', () => 'Hello Elysia')
.listen(3000)
export type App = typeof app
console.log(`🦊 Elysia 正在运行于 ${app.server?.hostname}:${app.server?.port}`)
或使用社区 OAuth 2.0 插件。
总之,Elysia 是学习/使用 Bun 及其生态系统的绝佳起点。
如果您想了解更多 Elysia,Elysia 文档 是探索概念和模式的一个很好的起点。如果您遇到困难或需要帮助,请随时在 Elysia Discord 中与我们联系。
所有代码的仓库可以在 https://github.com/saltyaom/elysia-trpc-demo 找到,欢迎您随意尝试和反馈。