Prisma 中文文档 下载 App

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(详见官方文档)。