使用 Prisma Bindings 从 Node 脚本访问 Prisma 服务

原创2026-09-23 17:42:03417 阅读
文章标签:后端数据库GraphQL

使用 Prisma Bindings 从 Node 脚本访问 Prisma 服务

本教程面向已拥有运行中 Prisma 服务的开发者,讲解如何在一个轻量 Node 脚本中,通过 prisma-binding 生成的类型化 API(即 Prisma bindings)完成对 Prisma 服务的查询(query)、变更(mutation)与存在性检查(exists)等操作。读完本文,你将掌握从数据模型更新、项目目录搭建、GraphQL schema 下载,到编写并运行可复用的 Node 访问脚本的完整链路,并能理解 Prisma binding 底层如何基于 GraphQL schema 动态生成调用接口。

准备工作:你需要一个运行中的 Prisma 服务

本教程假设你已拥有一个可访问的 Prisma 服务,因此开始之前,请确保手里有该服务的 endpoint(端点地址)。如果你还不清楚如何快速搭建自己的 Prisma 服务,可以参考仓库中以下教程(对应 docs/1.13/03-Tutorials2/01-Setup-Prisma 目录):

提示:教程中的每一步操作都带有编号标记,避免遗漏;如果你只关心操作步骤本身,可以直接按指令逐步执行。

Step 1:更新数据模型

首先确保你的 Prisma 服务具有本教程后续步骤所需的数据模型。本教程假定你的数据模型存放在单一文件 datamodel.graphql 中,如果实际布局不同,请相应调整。

操作 1:打开 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!
}

操作 2:保存文件后,打开终端并进入 Prisma 服务根目录(即 prisma.yml 所在目录),运行以下命令更新其 GraphQL API:

prisma deploy

更新后,Prisma 服务的 GraphQL API 将为 User 与 Post 两种类型暴露完整的 CRUD 操作,同时允许你修改二者之间的 关系(relation):一个 User 拥有多条 Post(posts: [Post!]!),每条 Post 又必须归属一个 User(author: User!)。

从源码看:prisma deploy 之后发生了什么

prisma deploy 会依据 prisma.yml 中的配置将数据模型同步到 Prisma 服务,并重新生成对应的 GraphQL API。这里值得一提的细节是 @default(value: "false") 指令——它让 published 字段在创建 Post 时自动获得默认值 false,因此后续脚本创建文章时无需显式传入该字段。这一点在 Step 4 的示例输出中可以直接看到:createPost 返回的对象里 published: false,正是默认值起作用的证据。

Step 2:设置项目目录结构

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

操作 1:进入一个新目录,在终端中依次执行:

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

操作 2:接下来,把 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

操作 3:安装 prisma-binding 及 GraphQL 核心库:

yarn add prisma-binding graphql

prisma-binding 是本教程的主角——它是从 Prisma schema 生成类型化 JavaScript API 的运行时库,graphql 则提供底层 schema 解析与文档构建能力。从本仓库的 prisma-client-lib 包 源码可以看到,Prisma 绑定实例在构造时会调用 buildSchema(typeDefs) 解析传入的 schema,并通过 BatchedGraphQLClient 与 SubscriptionClient 分别建立查询/变更与订阅通道,这正是"绑定实例 = 面向 Prisma 服务的 JavaScript SDK"这一说法的实现基础。

Step 3:下载 Prisma database schema

下一步是把 Prisma GraphQL API 的 schema(即 Prisma database schema)下载到本地项目,供 Prisma binding 指向使用。该操作借助 GraphQL CLI 与 GraphQL Config 完成。

操作 1:全局安装 GraphQL CLI:

yarn global add graphql-cli

操作 2:在 my-node-script 目录(服务根目录)创建 .graphqlconfig.yml:

touch .graphqlconfig.yml

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

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

此文件提供的信息会被 GraphQL CLI 以及 GraphQL Playground 共同使用:schemaPath 指定下载后 schema 的落盘位置,extensions.prisma 则指向你的 prisma.yml,让 CLI 知道从哪里读取 endpoint 等连接信息。

操作 4:运行以下命令,将 Prisma database schema 下载到 src/generated/prisma.graphql:

graphql get-schema

下载完成后,定义了你数据库完整 CRUD API 的 Prisma database schema 就位于 projects.prisma.schemaPath 属性指定的位置(即 src/generated/prisma.graphql)。

💡 Pro tip:如果你希望每次部署(例如更新数据模型)后 schema 都自动刷新,可以在 prisma.yml 中添加如下 post-deployment hook。hooks 是 prisma.yml 中可选的配置项,用于在 Prisma CLI 特定动作前后执行终端命令,当前可用的是 post-deploy(在 prisma deploy 之后触发),其类型为对象,键名与可用钩子一一对应。更完整的说明见 prisma.yml 配置参考:

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

仓库中 YAML-Structure.md 还给出了 post-deploy 同时执行多个任务的示例,例如打印部署完成信息、为 db project 下载 schema、执行代码生成等,读者可自行参考扩展。

Step 4:使用 Prisma binding 发送查询与变更

本步骤将正式使用 Prisma binding 与你的 Prisma 服务通信。

首先,通过指向上一步下载的 schema 来实例化一个 Prisma binding;由于绑定实例需要连接到你的 Prisma API,因此还必须提供 Prisma API 的 endpoint(该值可从 prisma/prisma.yml 中查到)。绑定实例充当 Prisma 服务的"JavaScript SDK",你可以调用其 API 向 Prisma 数据库发送查询与变更。

操作 1:将以下代码写入 src/index.js。它创建了一个 Prisma 绑定实例,并依次执行创建用户、查询用户、创建文章、更新文章、级联查询以及批量删除等操作:

⚠️ Important:务必把 __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 }

操作 2:运行脚本,观察终端打印的查询结果:

node src/index.js

理解绑定 API 的调用约定

从代码可以看出 Prisma binding 的两个关键调用形态:

  • query / mutation:每个数据模型类型对应一组方法,例如 users、createUser、updatePost、deleteManyPosts。方法签名通常为 (args, selectionSet)——第一个参数是 GraphQL 变量(如 data、where),第二个参数是你想要返回的字段选择集(如 "{ id name }")。绑定实例在内部会基于下载的 schema 解析出对应字段与类型,动态生成 GraphQL 文档后发送请求。
  • 关系写入:创建文章时通过 author: { connect: { id: ... } } 关联已存在的 User,这是 Prisma 处理关系型嵌套写入的标准语法——connect 表示"连接到已有节点",而 create 则表示"同时新建关联节点"。

从 Client.ts 的实现看,query、mutation、$subscribe、$graphql、$exists 等属性均在构造阶段通过 buildMethods() 等内部方法构建:每个调用先记录为一组 "instructions",再经 getDocumentForInstructions 生成完整 GraphQL 文档并交由底层的 BatchedGraphQLClient 发送;同时它支持通过 secret 配置签发 JWT 令牌,以 Authorization: Bearer <token> 请求头完成鉴权。也就是说,你使用的"类型化 SDK"本质上是基于 schema 动态生成的、对 GraphQL 请求的封装层。

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。

操作 1:将以下代码写入 src/index.js。它实例化 Prisma binding,创建用户与文章后使用 exists 验证节点存在性,删除数据后再验证节点已不存在:

⚠️ Important:务必把 __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

操作 2:运行脚本,观察终端打印的结果:

node src/index.js

exists 的典型用途

exists 非常适合在写入前做幂等校验或条件判断,例如"用户名是否已注册""文章标题是否已存在"这类场景。它避免了手写完整的 users(where: ...) 查询再自行判空的样板代码,返回值语义清晰(布尔值),可直接用于业务分支。在仓库的 Client.ts 中,$exists 由 buildExists() 构建,内部同样基于 schema 中每个类型的 where 过滤参数生成对应查询,其实现与 query/mutation 共享同一套指令与文档生成管线。

Step 6:以字符串形式发送原始查询与变更

Prisma binding 还允许你通过 request 属性,把完整的查询/变更以字符串形式直接发给 Prisma 服务。这种方式更为冗长——你需要拼写完整操作,且响应会多一层开销:因为返回结果会以查询/变更的 名称 作为顶层 key 包裹。

说明:Prisma binding 的 request 底层使用 graphql-request,因此与它拥有相同的 API 形态,即 request(query, variables?)。

操作 1:将 src/index.js 的内容替换为以下代码:

⚠️ Important:务必把 __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' } ] }
  */

操作 2:运行脚本,观察终端打印的结果:

node src/index.js

对比:request 与类型化 API

对比 Step 4 与 Step 6 的输出可以清晰看到差异:

  • 类型化调用(prisma.query.users、prisma.mutation.createUser)返回的是字段选择集直接对应的结果,结构扁平、无需关心操作名。
  • 原始 request 调用则需要手写完整 GraphQL 文档(可含变量定义与操作名),其响应以操作名作为顶层 key(如 { createUser: {...} }、{ users: [...] }),并且需要自行把变量作为第二个参数传入。

因此,request 更适合"有现成 GraphQL 字符串/文档"或"需要动态拼接操作"的场景,而类型化 API 在开发体验与代码可读性上更胜一筹。

小结

本教程带你走完了"用 Node 脚本访问 Prisma 服务"的完整流程:

  1. 更新数据模型(datamodel.graphql + prisma deploy),让 Prisma 服务暴露 User/Post 的 CRUD API;
  2. 搭建项目结构,将 Prisma 服务目录迁入脚本项目,安装 prisma-binding 与 graphql;
  3. 下载 Prisma database schema(.graphqlconfig.yml + graphql get-schema),并可选配置 post-deploy hook 实现自动刷新;
  4. 类型化调用 prisma.query / prisma.mutation 完成增删改查与关系连接;
  5. 存在性检查 prisma.exists.User(...) / prisma.exists.Post(...),以布尔值快速判断节点是否存在;
  6. 原始请求 prisma.request(query, variables),以字符串形式发送完整 GraphQL 文档。

这套能力可以无缝嵌入定时任务、数据迁移脚本、CI 校验、批量数据处理等场景,让 Node 脚本成为 Prisma 数据库的"一等公民"。对绑定实例内部机制感兴趣的读者,可继续阅读本仓库 prisma-client-lib 包 的源码,深入理解 schema 解析、指令生成与请求批处理的具体实现。

登录后查看全文
prisma1