Prisma Client 扩展:$extends 打造定制化查询 API
什么是 Client Extensions
Prisma Client Extensions 让你在不修改 Schema 的情况下,给生成的 PrismaClient 增加自定义行为。它用 $extends() 方法返回一个新的、带扩展的 PrismaClient 实例。
两种扩展类型
| 扩展类型 | 作用 | 类比 |
|---|---|---|
| 查询扩展(query) | Hook 到查询生命周期中,修改传入参数或返回结果 | 中间件 / AOP |
| 模型扩展(model) | 给特定 model 添加你自己的方法 | 添加自定义实例方法 |
查询扩展:日志
最常见的用法——记录每条数据库查询的耗时:
const prisma = new PrismaClient().$extends({
query: {
async $allOperations({ model, operation, args, query }) {
const start = Date.now()
const result = await query(args)
const duration = Date.now() - start
if (duration > 50) {
console.warn(`[SLOW] ${model}.${operation} took ${duration}ms`)
}
return result
},
},
})
查询扩展支持的钩子
| 钩子 | 触发时机 |
|---|---|
before | 查询执行前,可以修改 args |
after | 查询执行后,可以修改返回值 |
const prisma = new PrismaClient().$extends({
query: {
user: {
async findMany({ args, query }) {
args.where = { ...args.where, deletedAt: null }
return query(args)
},
},
},
})
// 之后所有 prisma.user.findMany() 都自动加了 deletedAt: null 过滤
软删除扩展(经典用例)
让 delete() 变成 update({ deletedAt: now() }),同时让所有查询自动排除已删除的记录:
const softDelete = new PrismaClient().$extends({
query: {
$allOperations({ model, operation, args, query }) {
if (model && operation === 'delete') {
// 把 delete 转换成 update + deletedAt
return query({
...args,
operation: 'update',
args: {
where: args.where,
data: { deletedAt: new Date() },
},
})
}
return query(args)
},
},
})
多租户扩展
根据当前请求的 tenantId 自动给所有查询加过滤条件:
export function createTenantedPrisma(tenantId: string) {
return basePrisma.$extends({
query: {
$allOperations({ args, query }) {
if (args.where && typeof args.where === 'object') {
args.where = { ...args.where, tenantId }
}
return query(args)
},
},
})
}
// 在每个请求里创建一个绑定 tenantId 的实例
const prismaForThisRequest = createTenantedPrisma(req.headers['x-tenant-id'])
模型扩展:自定义方法
给特定 model 添加自定义方法,调用起来像原生 API:
const prisma = new PrismaClient().$extends({
model: {
user: {
async findByEmail(email: string) {
return this.findUnique({ where: { email } })
},
async isAdmin(userId: number) {
const user = await this.findUnique({
where: { id: userId },
select: { role: true },
})
return user?.role === 'ADMIN'
},
},
},
})
// 直接调用
const user = await prisma.user.findByEmail('alice@prisma.io')
const isAdmin = await prisma.user.isAdmin(42)
组合多个扩展
扩展可以链式叠加:
const prisma = new PrismaClient()
.$extends({ query: { /* 日志扩展 */ } })
.$extends({ query: { /* 软删除扩展 */ } })
.$extends({ model: { /* 自定义方法 */ } })
或者把公共扩展写成可复用的函数:
export function withLogging(base: PrismaClient) {
return base.$extends({ /* ... */ })
}
export function withSoftDelete(base: PrismaClient) {
return base.$extends({ /* ... */ })
}
Prisma 7 的新中间件
Prisma ORM v8 引入了新的 middleware API(替代 v6 及更早版本的 $use 方法),与 Client Extensions 的概念类似但 API 不同。如果你的项目是 Prisma 7,用 Client Extensions 的 $extends;如果是 Prisma 8,用新的 middleware(详见官方文档)。