Fastify 模块化项目实战(三)
在搭建模块的时候, 与数据库进行交互是很常见的场景; 因为如果你的模块是与业务相关, 那么存储用户数据是重要的一环; 虽然openclaw的memory策略很聪明, 用md文件进行存储; 然而大部分的系统, 关系型数据库依然是非常趁手的工具;
对于使用TypeScript的项目来说,Prisma已经成为最受欢迎的ORM方案之一:
- 在 npm 上下载量位居 ORM 之首(社区广泛采纳)
- 被现代全栈框架与平台作为默认或推荐的数据层工具(生态认可)
- 在开发者调查中具有高认知与高留存率(实际使用率高)
- 与 TypeScript 有深度契合,提供真正的类型安全(开发体验显著提高
所以当我需要和 Fastify 这样的高性能 HTTP 框架搭配使用时, 果然抛弃了supabase平台, 转战了prisma。当然抛弃supabase的更重要的原因是模块已经是后端系统, 不再特别依赖BaaS平台的产品了; 这篇文章主要介绍Prisma的相关特性;
什么是 Prisma ORM
Prisma 是一个现代ORM(对象关系映射)工具集,它通过一个声明式的 schema 文件和自动生成客户端的方式来与数据库交互。Prisma支持PostgreSQL、MySQL、SQLite、SQL Server 等主流关系数据库。它生成的客户端是完全类型安全的TypeScript API,不需要你手写类型定义。
Prisma 的核心组件包括:
- Prisma Schema:一个声明式 DSL,用于定义数据模型
- Prisma Client:自动生成的类型安全查询客户端
- Prisma Migrate:数据库迁移工具
- Prisma Studio:可视化数据库浏览器
Prisma 的显著优势
类型安全(首要优势)
Prisma 的最大亮点是它与 TypeScript 的深度集成。无论是查询参数还是返回数据类型,都由生成的 Prisma Client 自动推断,无需手动写接口或类型定义。
const user = await prismaClient.user.findUnique({ where: { email: 'tui@lower-fat.io' } })
// user 类型是 { id: bigint, email: string, ... }
声明式数据建模
通过 schema.prisma 文件定义模型可以一眼看出数据库结构:
model User {
id BigInt @id
email String @unique
name String
hashed_password String
status String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Prisma 会自动根据schema定义的model生成对应的SQL语句, 例如以下是自动生成的语句:
-- CreateTable
CREATE TABLE "User" (
"id" BIGINT NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT NOT NULL,
"hashed_password" TEXT NOT NULL,
"status" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
自动迁移与版本控制
Prisma Migrate 可以根据 schema 变化自动生成 SQL 迁移脚本,并将它们纳入版本控制,减少手动维护成本, 这就像使用类似flyway等工具, 能将数据库表的每一次改动都能够记录起来, 像 git commits管理代码历史一般, prisma migrate管理sql的变化历史;
与 Fastify / Node.js 深度集成
Prisma Client 可以无缝用于 Fastify 路由处理器中查询数据库,无需手动映射任何类型或装饰器。但你希望用插件管理也完成可以, 目前fastify的项目还没有官方的fastify-prisma库, 不过有一些开源的第三方库;
Prisma 与 Fastify 集成的常用命令
官方文档写的非常详细: 官网
安装依赖
来自上面的官网:
npm install prisma @types/node --save-dev
npm install @prisma/client @prisma/adapter-pg dotenv
初始化 Prisma
来自上面的官网:
npx prisma init --db
这会创建:
prisma/schema.prisma.env(包含DATABASE_URL)- 生成的代码 (/generated/prisma)
- 会把
/generated/prisma放入.gitignore
修改 Prisma Schema
prisma/schema.prisma 示例:
datasource db {
provider = "postgresql"
}
generator client {
provider = "prisma-client-js",
output = "../src/modules/user/generated/prisma"
}
model Post {
id Int @id
title String
content String
}
注: Prisma 7.x 及以上版本已经不会再把URL定义到datasource db里面了, 网上的例子基本上全都是有url的, 但是如果你的prisma版本是7.x, 记得不要添加了
另外, output的路径是可以修改的, 如果你的项目有多个不同的模块, 它们彼此独立, 那么可以创建多个prisma client, 类似:
generator userClient {
provider = "prisma-client"
output = "../src/modules/user/generated/prisma"
}
generator postClient {
provider = "prisma-client"
output = "../src/modules/post/generated/prisma"
}
记得都把它们加到.gitignore里面, 这是官方建议的, 接着在workflow里通过prisma generate自动生成, 这样的优点是跨平台
生成 Prisma Client
npx prisma generate
每次 schema 改动后都要运行此命令更新客户端。执行完之后会生成以下代码:

运行数据库迁移
npx prisma migrate dev --name init
一般这是你初始化migrations文件夹的第一步, 这会创建并执行 SQL 迁移。 执行完之后的效果如下:
prisma/
├── migration_lock.toml
└── migrations/
└── 20260302093302_init/
└── migration.sql
更多命令请见官网的介绍.

在 Fastify 插件中管理 Prisma 实例
优雅管理 Prisma 生命周期:
import fp from 'fastify-plugin'
import { FastifyInstance } from 'fastify'
import { Pool } from 'pg'
import { PrismaPg } from '@prisma/adapter-pg'
import { PrismaClient } from '../../generated/prisma/client'
declare module 'fastify' {
interface FastifyInstance {
prismaClient: PrismaClient
}
}
export default fp(async (fastify: FastifyInstance) => {
const connectionString = `${fastify.config.DATABASE_URL}`
const pool = new Pool({ connectionString })
const adapter = new PrismaPg(pool)
const prisma = new PrismaClient({
adapter,
log: ['error']
})
fastify.decorate('prismaClient', prisma)
fastify.addHook('onClose', async () => {
await prisma.$disconnect()
})
}, {
name: 'prisma-client'
})
这允许你在所有路由中通过 fastify.prismaClient 访问数据库。
在 package.json 的scripts中配置 DATABASE_URL
一般情况下官网推荐你通过npx执行prisma的命令, 好处是本地不需要安装prisma, 不过一般容易让人忽视的问题是指定DATABSE_URL, 如果你连接的是本地的数据库, 那么可以使用:
DATABASE_URL="postgresql://{your-db-user-name}:{your-db-password}@localhost:5432/{your-db-name}?schema=public" npx prisma db seed
类似上面的命令, 否则会默认连接到远端的host: db.prisma.io:5432.
你也可以在package.json的scripts加入:
"migrate:dev": "cross-env POSTGRES_HOST=localhost prisma migrate dev --skip-seed",
"migrate:prod": "prisma generate && prisma migrate deploy",
"migrate:reset": "cross-env POSTGRES_HOST=localhost prisma migrate reset --skip-seed",
"studio": "cross-env POSTGRES_HOST=localhost prisma studio",
"seed:dev": "cross-env POSTGRES_HOST=localhost prisma db seed -- --environment development",
"seed:prod": "prisma db seed -- --environment production",
常见问题与解决思路
Prisma 即便强大,在实际开发中也会碰到一些问题。
Schema 大时类型生成缓慢或编辑器卡顿
随着模型数量增多,Prisma 生成的客户端类型可能非常庞大,导致 IDE 自动补全和 TypeScript server 卡顿。
思路:
- 分模块提升开发体验
- 限制导入范围,避免单文件过大
同时注意性能基准在某些情况下显示 ORM 性能不如其他库,但优势仍然是类型安全和开发体验。
在 Serverless(Lambda)环境下连接问题
在 serverless 环境中 Prisma 会对连接数管理不太友好,可能导致连接泄漏。
思路:
- 在全局作用域缓存 PrismaClient
- 使用连接池中间件如 PgBouncer
复杂联表查询难以优化
Prisma 支持的查询方式和关系包括 include 和 select,但对某些复杂 JOIN 的表达不如 SQL 自由。
思路:
- 对复杂查询使用
prisma.$queryRaw编写原生 SQL - 对性能敏感的查询进行手动优化
连接超时或数据库错误
Prisma 的数据库连接出错时可能抛出如 P1001 类错误。
思路:
- 确保
DATABASE_URL设置正确 - 调整数据库连接池配置
- 使用错误重试机制
小结
在 Fastify + TypeScript 技术栈中使用 Prisma 有以下明显好处:
- 完全类型安全:Prisma Client 自动生成类型,让开发体验一流。
- 简洁且强大的数据建模:无需手写接口与类型定义,schema 与代码同步。
- 自动迁移:Prisma Migrate 自动化管理数据库版本。
- 与 Fastify 无缝集成:可通过插件模式管理生命周期。
当然,Prisma 并非在所有场景都完美,有时候对于极其复杂查询或 serverless 用例需要额外优化或 fallback 原生 SQL。但总体而言,它极大提升了TypeScript开发体验与安全性,是构建高质量 Node.js API 服务的不错选择。
- ← Previous
三月番外篇 - 你真的知道多线程在后端服务(SpringX)是如何被处理的吗?