使用 Prisma Bindings 在 Node 脚本中访问 Prisma 服务:从数据模型、Schema 下载到查询与变更的完整实战

原创2026-09-21 09:46:571,019 阅读
文章标签:后端数据库GraphQL

使用 Prisma Bindings 在 Node 脚本中访问 Prisma 服务:从数据模型、Schema 下载到查询与变更的完整实战

本教程面向已经拥有一个可运行 Prisma 服务(具备可用 endpoint)的开发者,讲解如何从零搭建一个 Node 脚本项目,借助 prisma-binding 以“自动生成的 SDK”方式访问 Prisma GraphQL API。你将掌握:如何更新 Prisma 数据模型并重新部署、如何用 GraphQL CLI 把 Prisma 数据库 Schema 下载到本地、如何实例化 Prisma binding 发送查询/变更,以及 exists 与 request 两个高级入口的用法。文中所有示例均可在 Node.js 环境中直接运行验证。

前置条件

本教程假设你已经有一个正在运行的 Prisma 服务,并且知道它的 endpoint(服务地址)。如果你还没有准备好自己的 Prisma 服务,可以参考仓库中以下 Setup 教程之一先完成搭建:

本教程中所有“需要你亲自动手”的步骤都配有编号操作说明;如果你只想快速跑通而不关心背后的原理,可以直接按编号步骤顺序执行。

Step 1:更新数据模型

教程后续步骤依赖特定的数据模型,因此第一步先确保现有 Prisma 服务的数据模型符合要求。这里假设你的数据模型保存在单个文件 datamodel.graphql 中——如果不是,请先调整你的项目结构。

① 打开 datamodel.graphql,将内容更新为:

type User {
  id: ID! @unique
  name: String!
  posts: [Post!]!
}

type Post {
  id: ID! @unique
  title: String!
  content: String!
  published: Boolean! @default(value: "false")
  author: User!
}

这里用到两个 Prisma 数据模型中的关键指令:

  • @unique:标注该字段的值在数据库中唯一,id 字段因此成为每个节点的稳定主键;
  • @default(value: "false"):为 published 字段声明默认值,新建 Post 节点时无需显式传入。

同时,User.posts 与 Post.author 构成了 User ↔ Post 之间的双向关系。

② 保存文件后,打开终端,进入 Prisma 服务根目录(即 prisma.yml 所在目录),运行:

prisma deploy

prisma deploy 会把数据模型同步到 Prisma 服务并更新其 GraphQL API。此后,你的 Prisma 服务将为数据模型中的 User 与 Post 类型暴露完整的 CRUD 操作,并允许修改两者之间的关系(relation)。

从仓库源码可以看到,deploy 命令在完成迁移后会执行 post-deploy 钩子(见 cli/packages/prisma-cli-core/src/commands/deploy/deploy.ts#L311-L343),这正是我们后面用来自动刷新本地 Schema 的机制。

Step 2:搭建项目目录

数据模型更新完成后,开始搭建脚本项目的目录结构。

① 新建一个目录,并在终端中粘贴以下命令:

mkdir -p my-node-script/src
touch my-node-script/src/index.js
cd my-node-script
yarn init -y

② 接下来,把 Prisma 服务的根目录移动到 my-node-script 内并重命名为 prisma:

cd ..
mkdir my-node-script/prisma
mv datamodel.graphql prisma.yml my-node-script/prisma
cd my-node-script

此时项目的目录结构如下:

.
└── my-node-script
    ├── package.json
    ├── prisma
    │   ├── datamodel.graphql
    │   └── prisma.yml
    └── src
        └── index.js

③ 安装 prisma-binding:

yarn add prisma-binding graphql

prisma-binding 是专为 Prisma GraphQL API 设计的 GraphQL binding,可以把它理解成 Prisma 服务的“自动生成 SDK”:你不需要手写 SQL 或直接访问数据库 API,大部分数据库操作在 resolver 里都是一行调用。graphql 是它解析 Schema 所依赖的运行时库。

Step 3:下载 Prisma 数据库 Schema

下一步要把 Prisma GraphQL API 的 GraphQL Schema(即 Prisma database schema,描述了你数据库的完整 CRUD API)下载到本地项目中,供 Prisma binding 指向使用。下载动作由 GraphQL CLI 和 GraphQL Config 配合完成。

① 全局安装 GraphQL CLI:

yarn global add graphql-cli

② 在项目根目录(my-node-script)创建 .graphqlconfig:

touch .graphqlconfig.yml

③ 向其中写入以下内容,把 Prisma 的 GraphQL API 定义为一个 project:

projects:
  prisma:
    schemaPath: src/generated/prisma.graphql
    extensions:
      prisma: prisma/prisma.yml

这个文件提供的信息同时被 GraphQL CLI 和 GraphQL Playground 使用:

  • projects.prisma.schemaPath:声明 Prisma 数据库 Schema 下载后要保存到的位置;
  • projects.prisma.extensions.prisma:指向 prisma.yml,让 GraphQL CLI 知道如何连接你的 Prisma 服务(endpoint 等连接信息都从那里读取)。

④ 运行以下命令,把 Prisma 数据库 Schema 下载到 src/generated/prisma.graphql:

graphql get-schema

命令执行完毕后,定义了数据库完整 CRUD API 的 Prisma 数据库 Schema 就会出现在 projects.prisma.schemaPath 指定的位置(即 src/generated/prisma.graphql)。

💡 进阶技巧:如果你希望每次向 Prisma 服务部署变更(例如更新数据模型)后 Schema 都自动刷新,可以在 prisma.yml 中添加一个 post-deploy 钩子(钩子说明见 prisma.yml YAML 结构):

hooks:
  post-deploy:
    - graphql get-schema -p prisma

关于该钩子的底层执行:deploy 命令会在迁移成功后读取 post-deploy 钩子列表,并通过 spawnSync 逐条执行其中的终端命令(见 deploy.ts#L311-L343);钩子的读取逻辑位于 cli/packages/prisma-yml/src/PrismaDefinition.ts#L385-L394,它支持把单个字符串或字符串数组统一规范化为命令列表。post-deploy 是 Prisma CLI 目前提供的钩子类型,除了刷新 Schema,也可以用来在部署后执行 echo、代码生成等其他终端命令(见 prisma.yml YAML 结构中的示例)。

Step 4:发送查询与变更

现在进入核心环节——用 Prisma binding 与 Prisma 服务通信。

首先,通过指向上一步下载的 Schema 来实例化一个 Prisma binding。binding 实例需要连接你的 Prisma API,因此还要提供 endpoint(它存储在 prisma/prisma.yml 中)。这个 binding 实例相当于你 Prisma 服务的 JavaScript SDK,可以借助它的 API 向 Prisma 数据库发送查询和变更。

① 在 src/index.js 中写入以下代码。它创建了一个 Prisma binding 实例,并用它向 Prisma 服务发送查询和变更:

⚠️ 重要:请务必将 __YOUR_PRISMA_ENDPOINT__ 替换为你的 Prisma endpoint(即 prisma/prisma.yml 中存储的值)。

const { Prisma } = require("prisma-binding")

const prisma = new Prisma({
  typeDefs: "src/generated/prisma.graphql",
  endpoint: "__YOUR_PRISMA_ENDPOINT__"
})

prisma.mutation
  .createUser({ data: { name: "Alice" } }, "{ id name }")
  .then(console.log)
  // { id: 'cjhcidn31c88i0b62zp4tdemt', name: 'Alice' }
  .then(() => prisma.query.users(null, "{ id name }"))
  .then(response => {
    console.log(response)
    // [ { id: 'cjhcidn31c88i0b62zp4tdemt', name: 'Alice' } ]
    return prisma.mutation.createPost({
      data: {
        title: "Prisma rocks!",
        content: "Prisma rocks!",
        author: {
          connect: {
            id: response[0].id
          }
        }
      }
    })
  })
  .then(response => {
    console.log(response)
    /*
      { id: 'cjhcidoo5c8af0b62kv4dtv3c',
        title: 'Prisma rocks!',
        content: 'Prisma rocks!',
        published: false }
    */
    return prisma.mutation.updatePost({
      where: { id: response.id },
      data: { published: true }
    })
  })
  .then(console.log)
  /*
    { id: 'cjhcidoo5c8af0b62kv4dtv3c',
      title: 'Prisma rocks!',
      content: 'Prisma rocks!',
      published: true }
  */
  .then(() => prisma.query.users(null, "{ id posts { title } }"))
  .then(console.log)
  // [ { id: 'cjhcidn31c88i0b62zp4tdemt', posts: [ [Object] ] } ]
  .then(() => prisma.mutation.deleteManyPosts())
  .then(console.log)
  // { count: 1 }
  .then(() => prisma.mutation.deleteManyUsers())
  .then(console.log)
// { count: 1 }

这段代码串起了一条完整的“创建 → 查询 → 关联 → 更新 → 级联查询 → 清理”链路:

  1. prisma.mutation.createUser 创建名为 Alice 的用户,第二个参数 "{ id name }" 是选择集(selection set),用来指定返回哪些字段;
  2. prisma.query.users 查询所有用户,注意参数是 (null, ...)——第一个参数为 null 表示不传过滤条件;
  3. prisma.mutation.createPost 创建一篇帖子,并通过 author: { connect: { id: ... } } 把帖子关联到已存在的用户节点,这是 Prisma 中连接关系(relation connect)的标准写法;
  4. prisma.mutation.updatePost 通过 where: { id } 定位节点并更新 published 字段;
  5. prisma.mutation.deleteManyPosts / deleteManyUsers 批量删除,返回值是 { count }。

② 运行脚本,在终端中查看查询结果:

node src/index.js

关于 Prisma 实例构造参数的完整说明,可以参考仓库中的 Prisma Bindings API 参考:核心参数包括 typeDefs(Schema 文件路径,必填)、endpoint(服务地址,必填)、secret(服务密钥,可选)、debug(默认 false,设为 true 可将所有查询/变更打印到控制台)等。从仓库源码看,与 binding 同理念的客户端实现(cli/packages/prisma-client-lib/src/Client.ts)在内部通过 http-link-dataloader 提供的 BatchedGraphQLClient 统一发送请求(见 Client.ts#L20 与 this._client.request(query, variables) 调用点),因此这类客户端天然具备请求批处理能力——这也是“每个 delegate 函数调用最终都被翻译成一次 HTTP 请求”这一设计(见 Prisma Bindings API)的实现基础。

Step 5:用 exists 检查节点是否存在

除了 query 和 mutation,Prisma binding 还提供了一个非常方便的 exists 属性,用来检查数据库中是否存在满足特定条件的节点。exists 为数据模型中的每个类型暴露一个函数,函数名与类型同名(在本例中就是 prisma.exists.User(filter) 和 prisma.exists.Post(filter))。这些函数接收 filter 参数,并始终返回 true 或 false。

因为 Step 1 创建的数据模型包含 User 和 Post 两个模型,所以 Prisma 为你生成了 exists.User 和 exists.Post。

① 在 src/index.js 中写入以下代码:

⚠️ 重要:请务必将 __YOUR_PRISMA_ENDPOINT__ 替换为你的 Prisma endpoint(即 prisma/prisma.yml 中存储的值)。

const { Prisma } = require("prisma-binding")

const prisma = new Prisma({
  typeDefs: "src/generated/prisma.graphql",
  endpoint: "__YOUR_PRISMA_ENDPOINT__"
})

prisma.mutation
  .createUser({ data: { name: "Alice" } }, "{ id name }")
  .then(response => {
    return prisma.mutation.createPost({
      data: {
        title: "Prisma rocks!",
        content: "Prisma rocks!",
        author: {
          connect: {
            id: response.id
          }
        }
      }
    })
  })
  .then(() => prisma.exists.User({ name: "Alice" }))
  .then(response => console.log(response))
  // true
  .then(() => prisma.exists.Post({ title: "Prisma rocks" }))
  .then(response => console.log(response))
  // true
  .then(() => prisma.mutation.deleteManyPosts())
  .then(() => prisma.mutation.deleteManyUsers())
  .then(() => prisma.exists.Post({ title: "Prisma rocks" }))
  .then(response => console.log(response))
  // false
  .then(() => prisma.exists.User({ name: "Alice" }))
  .then(console.log)
  // false

这段脚本的验证路径非常清晰:

  • 创建用户 Alice 及其关联帖子后,exists.User({ name: "Alice" }) 与 exists.Post({ title: "Prisma rocks" }) 均返回 true;
  • 批量删除全部帖子与用户后,同样的两个 exists 调用都返回 false。

exists 非常适合用于“去重校验”“数据存在性前置判断”等场景。值得留意的是,exists 的过滤对象可以嵌套——例如 Prisma Bindings API 参考 中展示了 prisma.exists.Post({ id: 'abc', author: { name: 'Sarah' } }) 这种同时校验帖子自身属性及其关联作者属性的写法。

② 运行脚本,在终端中查看查询结果:

node src/index.js

Step 6:发送原始查询与变更

Prisma binding 还允许通过 request 属性,以字符串形式向 Prisma 服务发送查询和变更。这种方式更“啰嗦”——你必须完整拼写出整个查询/变更;同时响应也有少量额外开销,因为响应会包含查询/变更的名称作为顶层键(top level key)。

request 底层使用了 graphql-request,因此 API 与它保持一致。

① 用以下代码替换 src/index.js 的内容:

⚠️ 重要:请务必将 __YOUR_PRISMA_ENDPOINT__ 替换为你的 Prisma endpoint(即 prisma/prisma.yml 中存储的值)。

const { Prisma } = require("prisma-binding")

const prisma = new Prisma({
  typeDefs: "src/generated/prisma.graphql",
  endpoint: "__YOUR_PRISMA_ENDPOINT__"
})

const query = `
  {
    users {
      id
      name
    }
  }
`
const mutation = `
  mutation CreateUser($name: String!) {
    createUser(data: { name: $name }) {
      id
      name
    }
  }
`

const variables = { name: 'Bob' }

prisma.mutation
  .createUser({ data: { name: 'Alice' } }, '{ id name }')
  .then(console.log)
  // { id: 'cjhcijh30cgww0b622rwkkvbo', name: 'Alice' }
  .then(() => prisma.request(mutation, variables))
  .then(console.log)
  // { createUser: { id: 'cjhcijjndch0d0b62qux6o52a', name: 'Bob' } }
  .then(() => prisma.query.users(null, '{ id name }'))
  .then(console.log)
  /*
    [ { id: 'cjhciiacxcf850b62mcuaa3uz', name: 'Alice' },
      { id: 'cjhcijjndch0d0b62qux6o52a', name: 'Bob' } ]
  */
  .then(() => prisma.request(query))
  .then(console.log)
  /*
    { users:
      [ { id: 'cjhciiacxcf850b62mcuaa3uz', name: 'Alice' },
        { id: 'cjhcijjndch0d0b62qux6o52a', name: 'Bob' } ] }
  */

观察输出可以清楚看到 request 与 delegate 函数的差异:

  • prisma.mutation.createUser({ data: { name: 'Alice' } }, '{ id name }') 的返回是扁平的节点对象 { id, name };
  • 而 prisma.request(mutation, variables) 的返回多了一层操作名:{ createUser: { id, name } }。这里 mutation 字符串中定义了名为 CreateUser 的 mutation 和 $name 变量,variables 对象则负责提供变量的实际值;
  • prisma.request(query) 同理,返回 { users: [ ... ] } 而不是裸数组。

② 运行脚本,在终端中查看查询结果:

node src/index.js

更多关于 request 的用法示例(包括如何通过 prisma.request(query, variables) 传入变量并接收 {"data": {...}} 形式的响应)可以查阅 Prisma Bindings API 参考 中的 request 一节。

总结

通过本教程,你完成了一条完整的“从脚本访问 Prisma 服务”工作流:

环节 关键动作 产物
数据模型 编辑 datamodel.graphql + prisma deploy 包含 User/Post CRUD 与关系的 GraphQL API
项目搭建 yarn init -y + yarn add prisma-binding graphql 可运行的 Node 脚本项目
Schema 下载 配置 .graphqlconfig.yml + graphql get-schema 本地 src/generated/prisma.graphql
编程访问 prisma.query / prisma.mutation 类型化的查询/变更 delegate 函数
存在性检查 prisma.exists.User(filter) true/false 布尔结果
原始请求 prisma.request(query, variables) 兼容 graphql-request 的底层能力

核心要点回顾:

  • query 与 mutation 是自动生成的 delegate resolver,(args, info) 两参形式中 args 传操作参数、info 传选择集字符串(详见 API 参考);
  • exists 按类型生成存在性检查函数,可嵌套过滤条件;
  • request 以原始字符串方式发送查询/变更,与 graphql-request 同 API,响应带操作名顶层键;
  • 若希望在每次 prisma deploy 后自动刷新本地 Schema,可在 prisma.yml 中配置 post-deploy 钩子(钩子配置说明)。

现在,你完全可以在自己的 Node 脚本、定时任务或 GraphQL server 的 resolver 中,用这种“binding 即 SDK”的方式高效访问 Prisma 服务了。更系统的概念讲解(为什么用 binding 做 resolver 委托)可继续阅读 Prisma Bindings 概览。

登录后查看全文
prisma1