用 Prisma 与 graphql-yoga 实现常见 Resolver 模式:从数据模型扩展、Schema 同步到自定义变更
2026/9/23 23:07:00 网站建设 项目流程
  • 后端
  • 数据库
  • GraphQL

【免费下载链接】prisma1

💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated]

项目地址:https://gitcode.com/gh_mirrors/pr/prisma1
点击查看免费下载

本篇技术指南以 Prisma 1.x 时代的typescript-basicGraphQL boilerplate 项目为背景,系统讲解在graphql-yoga+ Prisma 架构中两个最常见的 Resolver 开发场景:如何为数据模型新增字段并将其暴露到应用 API,以及如何为 Mutation 添加自定义 Resolver。读完本文,你将掌握"数据模型 → Prisma 服务 → 应用 Schema → Resolver 委托"这条完整的链路操作,并理解ctx.db.mutation委托机制与exists权限校验模式的底层原理。

前置背景:两层 Schema 与 Resolver 委托

在 Prisma +graphql-yoga的典型架构中,存在两个独立的 GraphQL Schema,理解它们的区别是掌握 Resolver 模式的前提:

  • 数据模型(database/datamodel.graphql:用 SDL 定义数据库结构,是 Prisma 服务生成 CRUD API 的根基。Prisma 会基于它生成完整的Prisma Schema(在typescript-basic项目中存放于src/generated/prisma.graphql),包含对数据模型中每个类型生成的QueryMutationSubscription操作。
  • 应用 Schema(src/schema.graphql:定义你真正暴露给客户端应用的 GraphQL API,其中的字段、参数和返回类型由你自行裁剪,可以隐藏password之类的敏感字段,也可以只暴露部分数据库字段。

应用 Schema 的 Resolver 通常以"委托"方式实现——不直接写 SQL 或调用数据库驱动,而是把查询/变更转发给底层 Prisma 服务。正如 prisma-binding 概述 所描述的:prisma-binding为基于 Prisma 服务构建 GraphQL 服务器提供了一个便捷层,通过把执行委托给底层 Prisma 服务的 API,大多数 Resolver 可以写成一行代码。

typescript-basicboilerplate 的 入口文件 中,Prisma实例被注入到 GraphQL 服务器的context中,挂载为ctx.db

const server = new GraphQLServer({ typeDefs: './src/schema.graphql', // points to the application schema resolvers, context: req => ({ ...req, db: new Prisma({ endpoint: 'http://localhost:4466/my-app/dev', // the endpoint of the Prisma DB service secret: 'mysecret123', // specified in `database/prisma.yml` debug: true, // log all GraphQL queries & mutations }), }), })

因此,在每个 Resolver 的四个标准参数(parent, args, ctx, info)中,ctx.db就是通往 Prisma 服务的委托通道。

场景一:为数据模型新增字段并暴露到 API

第一个常见场景是:在数据库的User类型上新增一个address字段,并希望客户端应用也能通过 GraphQL API 读写它。整个过程分三步。

第 1 步:修改数据模型

打开database/datamodel.graphql,在User类型中追加address字段(注意 Prisma 的指令语法:@unique表示唯一约束,!表示非空):

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

这里address被定义为可空String,表示并非每个用户都必须填写地址。数据模型是"数据库真相的唯一来源",Prisma 服务端所有表结构、索引和 CRUD 能力都由它派生。

第 2 步:部署更新后的数据模型

在项目根目录执行:

prisma deploy

这条命令会完成两件事:

  • 部署新的数据库结构到本地服务:Prisma 服务根据最新的datamodel.graphql更新底层数据库表结构(在本地开发环境中,即部署到通过prisma local start启动的 local cluster,服务地址形如http://localhost:4466/my-app/dev);
  • 下载数据库的最新 GraphQL Schema 到database/schema.graphql:这个文件由 CLI 自动生成,永远不要手工编辑,任何结构变更都应通过修改datamodel.graphql并重新prisma deploy来完成。

第 3 步:把字段同步到应用 Schema

仅仅修改数据模型还不够——数据模型中的字段默认不会自动出现在客户端 API 中。你需要在src/schema.graphql中把address字段也加到应用 Schema 的User类型上:

type User { id: ID! email: String! name: String! posts: [Post!]! + address: String }

注意这里应用 Schema 刻意没有password字段,且id/email上没有@unique指令——应用 Schema 最终决定了客户端能看到什么数据。数据模型负责"数据库里有什么",应用 Schema 负责"API 对外暴露什么",这是 Prisma 双 Schema 架构最核心的设计思想,也是 权限教程 中"role字段不暴露给客户端"这一做法的理论基础。

完成上述三步后,重新启动服务器,客户端即可在查询和变更中访问User.address

场景二:添加一个自定义 Resolver(删除 Post)

第二个场景是典型的"应用层自定义操作":为 API 添加一个delete变更,用于删除一条Post记录。与数据库直连方案不同,这里你不需要写任何数据库删除语句,只需把参数转发给 Prisma 服务即可。

第 1 步:在应用 Schema 中声明 Mutation

src/schema.graphqlMutation类型中加入delete字段:

type Mutation { createDraft(title: String!, text: String): Post publish(id: ID!): Post + delete(id: ID!): Post }

参数id: ID!用于定位要删除的Post,返回类型为Post,因此调用方可以在删除后拿到被删节点的字段。

第 2 步:实现 Resolver

src/index.jsMutationresolvers 部分添加delete的实现:

delete(parent, { id }, ctx, info) { return ctx.db.mutation.deletePost( { where: { id } }, info ); }

这段代码值得逐点拆解:

  • 参数解构{ id }直接取出客户端传入的id
  • 委托调用ctx.db.mutation.deletePost是 Prisma 服务为数据模型中的Post自动生成的删除变更。它接收{ where: { id } }作为过滤条件;
  • 透传info:第二个参数info包含了客户端本次请求的完整 selection set(即客户端要返回哪些字段),Prisma 客户端会利用它构造精确的 GraphQL 文档,只查询客户端真正需要的字段,避免过度获取;
  • 返回透传deletePost返回删除后的Post节点,正好匹配应用 Schema 中声明的Post返回类型。

从源码层面看,这种deletePost({ where })的调用形态并不是手写约定,而是由 Prisma 客户端实现 自动包装的:客户端在buildMethods()中把 Prisma Schema 的QueryMutation字段动态挂载到ctx.db上,当字段名以delete开头时,会自动把用户传入的参数包装为{ where: realArgs }(见 Client.ts 中getTypesdelete前缀的处理);对create前缀的字段则自动包装为{ data: realArgs }。这解释了为什么示例代码中传给deletePost的第一个参数只需要写{ id }而不是完整的{ where: { id } }——方便之余,也意味着你需要对"客户端参数会被自动包装"这一行为有清晰认知。

第 3 步:启动服务器并验证

运行yarn start(或yarn dev以自动打开 GraphQL Playground)启动服务器,然后用下面的变更删除一条 Post(把__POST_ID__替换为实际记录 id):

mutation { delete(id: "__POST_ID__") { id } }

yarn dev启动后,Playground 会同时呈现两个 API:app(应用 Schema,即src/schema.graphql定义的 API)与database(Prisma CRUD API,即生成的 Prisma Schema)。你在app端发送delete变更,数据最终由 Prisma 服务持久化;也可以切换到database端直接发送deletePost(where: { id })来对照验证结果。

纵深:从"委托"到"校验"的 Resolver 演进

掌握委托模式后,一个自然的问题是:如何在不破坏委托简洁性的前提下,加入业务校验(例如权限控制)?Prisma 生态给出的答案是:在委托调用之前,先用ctx.db.exists做数据访问检查。这与本文场景二属于同一 Resolver 编写范式,只是多了一层前置判断:

async deletePost(parent, { id }, ctx, info) { const userId = getUserId(ctx) const postExists = await ctx.db.exists.Post({ id, author: { id: userId }, }) const requestingUserIsAdmin = await ctx.db.exists.User({ id: userId, role: 'ADMIN', }) if (!postExists && !requestingUserIsAdmin) { throw new Error(`Post not found or you don't have access rights to delete it.`) } return ctx.db.mutation.deletePost({ where: { id } }) }

exists是 Prisma 客户端生成的内建函数:它会对传入的where条件执行一次存在性查询并返回布尔值(其实现可见 Client.ts 中的buildExists,本质是调用对应模型的列表查询并判断结果是否非空)。通过exists.Post({ id, author: { id: userId } })判断"请求者是否为该 Post 的作者",通过exists.User({ id: userId, role: 'ADMIN' })判断"请求者是否为管理员",两者都失败才抛出权限错误,否则继续委托。这样,权限逻辑以可读的方式内联在 Resolver 中,而底层数据操作仍然是一行委托调用,完整示例可参考仓库中的 Permissions 教程。

总结

本文覆盖了graphql-yoga+ Prisma 开发中最常用的两类 Resolver 模式:

模式关键操作本质
新增字段并暴露datamodel.graphqlprisma deploy→ 同步src/schema.graphql数据模型与应用 Schema 的分层同步
新增自定义 Resolver应用 Schema 声明字段 → 用ctx.db.mutation.xxx委托实现把数据库操作委托给 Prisma 生成的 CRUD API

所有场景都建立在同一个核心心智模型上:数据模型定义数据库真相,应用 Schema 定义 API 边界,Resolver 则是连接两者的薄委托层。在此基础上叠加exists校验,即可把委托模式扩展为带权限控制的生产级 Resolver。相关项目结构与入口代码可继续参考 TypeScript Prisma Quickstart 与 prisma-binding 概述。

  • 后端
  • 数据库
  • GraphQL

【免费下载链接】prisma1

💾 Database Tools incl. ORM, Migrations and Admin UI (Postgres, MySQL & MongoDB) [deprecated]

项目地址:https://gitcode.com/gh_mirrors/pr/prisma1
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询