用 Elysia 加速你的下一个 Prisma 服务器

它提供了类型安全的直观 API,让我们可以使用流畅、自然的语法与数据库交互。
编写数据库查询就像用 TypeScript 编写数据形状一样简单,利用自动补全功能,然后 Prisma 会处理其余部分,在后台生成高效的 SQL 查询并管理数据库连接。
Prisma 的一个突出特性是它与流行数据库的无缝集成,例如:
- PostgreSQL
- MySQL
- SQLite
- SQL Server
- MongoDB
- CockroachDB
因此,我们可以灵活选择最适合项目需求的数据库,而不会牺牲 Prisma 带来的强大功能和性能。
这意味着你可以专注于真正重要的事情:构建你的应用逻辑。
Prisma 是 Elysia 的灵感来源之一,它声明式的 API 和流畅的开发者体验绝对是一大享受。
现在,随着 Bun 0.6.7 的发布,我们可以将期待已久的想象变为现实,Bun 现在开箱即用支持 Prisma。
Elysia
当你被问到“用 Bun 应该选择哪个框架”时,Elysia 是脑海中浮现的答案之一。
虽然你可以使用 Express 与 Bun 一起工作,但 Elysia 是专为 Bun 构建的。
Elysia 凭借声明式 API,可以比 Express 快近 19 倍,在创建统一类型系统和端到端类型安全方面表现出色。
Elysia 还以其流畅的开发者体验而闻名,尤其是 Elysia 从早期就设计为与 Prisma 一起使用。
借助 Elysia 的严格类型验证,我们可以使用声明式 API 轻松集成 Elysia 和 Prisma。
换句话说,Elysia 将确保运行时类型和 TypeScript 类型始终同步,让它表现得像严格类型语言,你可以完全信任类型系统,提前发现任何类型错误,并更容易调试与类型相关的错误。
设置
要开始,我们只需运行 bun create
来设置 Elysia 服务器
bun create elysia elysia-prisma
其中 elysia-prisma
是我们的项目名称(文件夹目标),可以随意更改为任何你喜欢的名字。
现在进入我们的文件夹,并将 Prisma CLI 安装为开发依赖。
bun add -d prisma
然后我们可以使用 prisma init
设置 Prisma 项目
bunx prisma init
bunx
是 Bun 的命令,等同于 npx
,它允许我们执行包的二进制文件。
设置完成后,我们会看到 Prisma 会更新 .env
文件并生成一个名为 prisma 的文件夹,里面有一个 schema.prisma 文件。
schema.prisma 是使用 Prisma 模式语言定义的数据库模型。
让我们像这样更新我们的 schema.prisma 文件作为演示:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
username String @unique
password String
}
告诉 Prisma 我们想要创建一个名为 User 的表,列如下:
列 | 类型 | 约束 |
---|---|---|
id | Number | 主键并自动递增 |
username | String | 唯一 |
password | String | - |
Prisma 将读取模式和 .env
文件中的 DATABASE_URL,因此在同步数据库之前,我们需要先定义 DATABASE_URL
。
由于我们没有运行任何数据库,我们可以使用 Docker 设置一个:
docker run -p 5432:5432 -e POSTGRES_PASSWORD=12345678 -d postgres
现在进入项目根目录的 .env
文件并编辑:
DATABASE_URL="postgresql://postgres:12345678@localhost:5432/db?schema=public"
然后我们运行 prisma migrate
来将我们的数据库与 Prisma 模式同步:
bunx prisma migrate dev --name init
Prisma 然后会基于我们的模式生成强类型 Prisma Client 代码。
这意味着我们在代码编辑器中获得自动补全和类型检查,在编译时捕获潜在错误,而不是运行时。
进入代码
在我们的 src/index.ts 中,让我们更新 Elysia 服务器来创建一个简单的用户注册端点。
import { Elysia } from 'elysia'
import { PrismaClient } from '@prisma/client'
const db = new PrismaClient()
const app = new Elysia()
.post(
'/sign-up',
async ({ body }) => db.user.create({
data: body
})
)
.listen(3000)
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
)
我们刚刚创建了一个简单的端点,使用 Elysia 和 Prisma 将新用户插入数据库。
TIP
重要提示:当返回 Prisma 函数时,你应该始终将回调函数标记为 async。
因为 Prisma 函数不返回原生 Promise,Elysia 无法动态处理自定义 Promise 类型,但通过静态代码分析,将回调函数标记为 async,Elysia 会尝试 await 函数的返回类型,从而允许我们映射 Prisma 结果。
现在问题是 body 可以是任何东西,不限于我们期望的定义类型。
我们可以通过使用 Elysia 的类型系统来改进这一点。
import { Elysia, t } from 'elysia'
import { PrismaClient } from '@prisma/client'
const db = new PrismaClient()
const app = new Elysia()
.post(
'/sign-up',
async ({ body }) => db.user.create({
data: body
}),
{
body: t.Object({
username: t.String(),
password: t.String({
minLength: 8
})
})
}
)
.listen(3000)
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
)
这告诉 Elysia 验证传入请求的 body 是否匹配形状,并更新回调中 body
的 TypeScript 类型以匹配完全相同的类型:
// 'body' 现在被键入为以下类型:
{
username: string
password: string
}
这意味着如果形状与数据库表不兼容,它会立即警告你。
这在需要编辑表或执行迁移时非常有效,Elysia 可以在到达生产环境之前逐行记录类型冲突错误。
错误处理
由于我们的 username
字段是唯一的,有时 Prisma 可能会抛出错误,例如在注册时意外重复 username
:
Invalid `prisma.user.create()` invocation:
Unique constraint failed on the fields: (`username`)
默认的 Elysia 错误处理器可以自动处理这种情况,但我们可以通过指定自定义错误使用 Elysia 的本地 onError
钩子来改进:
import { Elysia, t } from 'elysia'
import { PrismaClient } from '@prisma/client'
const db = new PrismaClient()
const app = new Elysia()
.post(
'/',
async ({ body }) => db.user.create({
data: body
}),
{
error({ code }) {
switch (code) {
// Prisma P2002: "Unique constraint failed on the {constraint}"
case 'P2002':
return {
error: 'Username must be unique'
}
}
},
body: t.Object({
username: t.String(),
password: t.String({
minLength: 8
})
})
}
)
.listen(3000)
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
)
使用 error
钩子,回调中抛出的任何错误都会填充到 error
钩子,允许我们定义自定义错误处理器。
根据 Prisma 文档,错误代码 'P2002' 表示执行查询时失败了唯一约束。
由于这个表只有一个唯一的 username
字段,我们可以推断错误是因为用户名不唯一,因此我们返回自定义错误消息:
{
error: 'Username must be unique'
}
当唯一约束失败时,这将返回我们自定义错误消息的 JSON 等价物。
允许我们从 Prisma 错误无缝定义任何自定义错误。
奖励:引用模式
当我们的服务器变得复杂,类型变得冗余并成为样板代码时,可以通过使用 Reference Schema 来改进内联 Elysia 类型。
简单来说,我们可以为模式命名并通过名称引用类型。
import { Elysia, t } from 'elysia'
import { PrismaClient } from '@prisma/client'
const db = new PrismaClient()
const app = new Elysia()
.model({
'user.sign': t.Object({
username: t.String(),
password: t.String({
minLength: 8
})
})
})
.post(
'/',
async ({ body }) => db.user.create({
data: body
}),
{
error({ code }) {
switch (code) {
// Prisma P2002: "Unique constraint failed on the {constraint}"
case 'P2002':
return {
error: 'Username must be unique'
}
}
},
body: 'user.sign',
body: t.Object({
username: t.String(),
password: t.String({
minLength: 8
})
})
}
)
.listen(3000)
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
)
这与使用内联相同,但你只需定义一次,然后通过名称引用模式来移除冗余验证代码。
TypeScript 和验证代码将按预期工作。
奖励:文档
作为奖励,Elysia 类型系统还符合 OpenAPI Schema 3.0,这意味着它可以使用支持 OpenAPI Schema 的工具生成文档,例如 Swagger。
我们可以使用 Elysia Swagger 插件在一行代码中生成 API 文档。
bun add @elysiajs/swagger
然后只需添加插件:
import { Elysia, t } from 'elysia'
import { PrismaClient } from '@prisma/client'
import { swagger } from '@elysiajs/swagger'
const db = new PrismaClient()
const app = new Elysia()
.use(swagger())
.post(
'/',
async ({ body }) =>
db.user.create({
data: body,
select: {
id: true,
username: true
}
}),
{
error({ code }) {
switch (code) {
// Prisma P2002: "Unique constraint failed on the {constraint}"
case 'P2002':
return {
error: 'Username must be unique'
}
}
},
body: t.Object({
username: t.String(),
password: t.String({
minLength: 8
})
}),
response: t.Object({
id: t.Number(),
username: t.String()
})
}
)
.listen(3000)
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
)
就这样,我们就可以为 API 创建一个定义良好的文档了。

而且由于为文档定义了严格类型,我们发现我们意外地从 API 返回了 password
字段,这不是一个好主意,因为返回私有信息是不合适的。
感谢 Elysia 的类型系统,我们定义响应不应包含 password
,这会自动警告我们 Prisma 查询返回了密码,让我们提前修复。
而且,如果有更多内容,我们不必担心可能会忘记 OpenAPI Schema 3.0 的规范,因为我们也有自动补全和类型安全。
我们可以使用 detail
定义路由细节,它也遵循 OpenAPI Schema 3.0,从而轻松创建文档。
接下来
借助 Bun 和 Elysia 对 Prisma 的支持,我们正在进入一个全新开发者体验时代。
对于 Prisma,我们可以加速与数据库的交互;Elysia 则在开发者体验和性能方面加速了后端 Web 服务器的创建。
这绝对是一种享受。
Elysia 正在努力为 Bun 创建一个新标准,以实现更好的开发者体验,构建高性能 TypeScript 服务器,其性能可以媲美 Go 和 Rust。
如果你想开始学习 Bun,不妨看看 Elysia 能提供什么,尤其是像 tRPC 那样的 端到端类型安全,但基于 REST 标准,没有任何代码生成。
如果你对 Elysia 感兴趣,欢迎查看我们的 Discord 服务器 或在 GitHub 上查看 Elysia