拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

How to GraphQL TypeScript/Apollo 实战:用 Nexus + Prisma 为 feed 查询实现过滤、分页与排序

How to GraphQL TypeScript/Apollo 实战:用 Nexus + Prisma 为 feed 查询实现过滤、分页与排序 【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载本文基于 How to GraphQL 教程仓库中content/backend/typescript-apollo/8-filtering-pagination-and-sorting.md一篇章节展开完整讲解如何为基于 TypeScript、Apollo Server、Nexus 与 Prisma 构建的 HackerNews 克隆项目feed查询添加过滤filtering、Limit-Offset 分页skip/take、多字段排序orderBy以及返回总数count的完整能力。读完后你可以掌握如何用 Nexus 的stringArg/intArg/inputObjectType/enumType构建类型安全的查询参数如何把 Prisma Client 的where、skip、take、orderBy、count选项正确透传给findMany以及为什么count与分页后返回的links数量会不一致这类设计细节。一、本章目标让 feed 查询支持过滤、分页与排序在这一系列的上一章你已经通过PrismaClient把feed查询接入了真实的 SQLite 数据库参见 连接服务器与数据库 中的context.prisma.link.findMany()。本章的目标是让客户端能够约束feed查询返回的Link列表过滤客户端提供一个过滤字符串只返回url或description中包含该子串的Link分页客户端提供skip偏移量与take条数两个参数按 Limit-Offset 模型分页排序客户端声明一个或多个排序条件字段 升/降序总数feed不再直接返回列表而是返回一个包含links、count、id的Feed对象。整个实现思路与系列前几章一致查询解析的重活由 Prisma 完成Nexus 只负责把 GraphQL 参数“透传”给context.prisma.link.findMany(...)。二、过滤新增可选的 filter 参数使用PrismaClient后实现过滤并不需要多少代码。关键设计决策是feed查询接受一个过滤字符串filter只返回url或description中包含该子串的Link两者满足其一即可即 OR 语义。在src/graphql/Link.ts中为feed查询添加新的字符串类型参数filter并更新 resolverexport const LinkQuery extendType({ type: Query, definition(t) { t.nonNull.list.nonNull.field(feed, { type: Link, args: { filter: stringArg(), // 1 }, resolve(parent, args, context) { const where args.filter // 2 ? { OR: [ { description: { contains: args.filter } }, { url: { contains: args.filter } }, ], } : {}; return context.prisma.link.findMany({ where, }); }, }); }, });注释解读// 1注意filter参数是可选的可选参数是stringArg()默认行为客户端可以省略它来跳过过滤// 2如果提供了filter参数就构造一个表达过滤条件的where对象——“description或url或两者中包含与过滤字符串匹配的子串”。这个where参数被 Prisma 用来筛掉不符合条件的Link元素如果没有提供filterwhere就是空对象行为与之前完全一致。变更之后Nexus 重新生成npm run generate得到的 GraphQL schema 中feed查询变为type Query { feed(filter: String): [Link!]! }可以用如下查询测试过滤功能query { feed(filter: nexus) { id description url postedBy { id name } } }预期返回类似{ data: { feed: [ { id: 1, description: Code-First GraphQL schemas for JavaScript/TypeScript, url: nexusjs.org, postedBy: { id: 1, name: alice } } ] } }建议多试几个过滤字符串。注意如果提供了一个不匹配任何link的过滤条件会收到一个空数组[]而不是报错——这是过滤语义的自然结果。三、分页Limit-Offset 模型与 skip / take 参数3.1 两种主流分页模型分页是 API 设计中的经典难题。从高层看主要有两种思路Limit-Offset限制-偏移通过提供要取回元素的索引实际上是起始索引offset和要取回的元素个数limit来请求列表中的特定“块”Cursor-based游标式更进阶的模型。列表中每个元素都关联一个唯一 ID即cursor分页的客户端提供起始元素的游标以及要取回的元素个数。Prisma 同时支持两种分页方式。本教程选择实现Limit-Offset 分页因为它与数据库的“跳过 取 N 条”能力直接对应实现成本最低。3.2 术语对应limit 是 takeoffset 是 skipLimit 和 offset 在 Prisma API 中有不同的名字limit叫take——从给定起始索引开始“取”takex个元素offset起始索引叫skip——先“跳过”skip列表里那么多元素再收集要返回的项目。如果未提供skip其默认值为0分页总是从列表开头开始。因此给feed查询添加skip与take两个参数并相应更新 resolverimport { extendType, idArg, nonNull, objectType, stringArg, intArg } from nexus; export const LinkQuery extendType({ type: Query, definition(t) { t.nonNull.list.nonNull.field(feed, { type: Link, args: { filter: stringArg(), skip: intArg(), // 1 take: intArg(), // 1 }, resolve(parent, args, context) { const where args.filter ? { OR: [ { description: { contains: args.filter } }, { url: { contains: args.filter } }, ], } : {}; return context.prisma.link.findMany({ where, skip: args?.skip as number | undefined, // 2 take: args?.take as number | undefined, // 2 }); }, }); }, });变更细节// 1skip和take都是可选的整型参数分别代表 offset 与 limit// 2Prisma Client API 会把skip与take作为findMany查询的附加选项据此返回link记录。若任一参数缺失就向 Prisma 传undefined。这里有一个类型不匹配问题Nexus 生成的参数类型是number | undefined | null而 Prisma 期望的是number | undefined因此需要as number | undefined做类型断言剥掉null这一分支。注意在 JavaScript 和 TypeScript 中undefined与null经常被混用但 Prisma 严格区分二者——在 Prisma 中null是一个具体的_值_而undefined表示“什么都不做”/忽略该选项。这是为什么透传时要统一使用undefined而非null的原因。更新后schema 中的feed查询变为type Query { feed(filter: String, skip: Int, take: Int): [Link!]! }可以用下面的查询测试分页 API它返回列表中的第二个Linkquery { feed(take: 1, skip: 1) { id description url } }预期返回{ data: { feed: [ { id: 2, description: Next-generation Node.js and TypeScript ORM, url: www.prisma.io } ] } }四、排序LinkOrderByInput 输入类型与 Sort 枚举4.1 定义排序选项类型Prisma 允许按特定标准返回排序ordered的元素列表。例如可以按url或description字母序排列Link列表且支持升序asc与降序desc。对 HackerNews API教程把“如何排序”完全交给客户端决定因此把 Prisma API 的全部排序选项都暴露到 GraphQL API 中——做法是创建一个input类型LinkOrderByInput和一个枚举Sortimport { extendType, nonNull, objectType, stringArg, intArg, inputObjectType, enumType, arg } from nexus; export const LinkOrderByInput inputObjectType({ name: LinkOrderByInput, definition(t) { t.field(description, { type: Sort }); t.field(url, { type: Sort }); t.field(createdAt, { type: Sort }); }, }); export const Sort enumType({ name: Sort, members: [asc, desc], });这会在 GraphQL schema 中生成以下类型input LinkOrderByInput { createdAt: Sort description: Sort url: Sort } enum Sort { asc desc }LinkOrderByInput表示列表可排序的标准字段Sort枚举定义排序方向。三个可排序字段description、url、createdAt恰好对应数据库Link模型中的业务字段id为自增主键一般不开放给用户排序。4.2 为 feed 添加 orderBy 参数在feed查询中新增orderBy参数并更新 resolverimport { extendType, nonNull, objectType, stringArg, intArg, inputObjectType, enumType, arg, list } from nexus; import { Prisma } from prisma/client export const LinkQuery extendType({ type: Query, definition(t) { t.nonNull.list.nonNull.field(feed, { type: Link, args: { filter: stringArg(), skip: intArg(), take: intArg(), orderBy: arg({ type: list(nonNull(LinkOrderByInput)) }), // 1 }, resolve(parent, args, context) { const where args.filter ? { OR: [ { description: { contains: args.filter } }, { url: { contains: args.filter } }, ], } : {}; return context.prisma.link.findMany({ where, skip: args?.skip as number | undefined, take: args?.take as number | undefined, orderBy: args?.orderBy as Prisma.EnumerablePrisma.LinkOrderByWithRelationInput | undefined, // 2 }); }, }); }, });两处变更// 1新的orderBy参数是LinkOrderByInput输入类型的数组。在其中可以提供一个或多个排序标准createdAt、description、url并指定排序方向asc或descfeed 中的链接将按此排序。按这个设计通过传入多个LinkOrderByInput实例可以实现多字段排序例如先按url排url相同时再按createdAt排// 2传给 Prisma 的orderBy选项与前面的skip/take类似由于 Nexus 生成的类型含null分支而 Prisma 期望Prisma.EnumerablePrisma.LinkOrderByWithRelationInput | undefined因此同样需要类型断言剥离null选项。用下面的查询测试按创建时间倒序排序query { feed(orderBy: [{ createdAt: desc }]) { id createdAt description url } }结果类似{ data: { feed: [ { id: 3, createdAt: 2021-12-15T04:20:33.616Z, description: Next-generation Node.js and TypeScript ORM, url: www.prisma.io }, { id: 1, createdAt: 2021-12-14T23:21:52.620Z, description: Code-First GraphQL schemas for JavaScript/TypeScript, url: nexusjs.org } ] } }建议到此为止可以再加几条 link 记录尝试多字段排序如orderBy: [{ url: asc }, { createdAt: desc }]另外把排序、过滤、分页组合起来filterskip/takeorderBy实验一下观察结果。注意三者的执行顺序是由 Prisma 决定的where先筛选orderBy再排序skip/take最后截取分页块——所以“先过滤再分页”天然成立。五、返回 Link 总数重构 feed 为 Feed 对象5.1 为什么需要 count最后一个功能是让 API 能够回答“数据库里当前有多少条Link”。为此需要把feed查询重构一下不再直接返回列表而是返回一个新的类型Feed。动机在于当使用take分页时_返回_的 links 数量可能与数据库中_可用_的 links 数量不同客户端需要一个独立的count字段来渲染分页器。5.2 定义 Feed 类型在Link.ts中创建新的Feed类型export const Feed objectType({ name: Feed, definition(t) { t.nonNull.list.nonNull.field(links, { type: Link }); // 1 t.nonNull.int(count); // 2 t.id(id); // 3 }, });各字段含义// 1links是Link类型对象的数组即当前feed查询的返回内容本身// 2count是整型表示数据库中匹配 feed 查询条件的link数量。这一点非常重要分页取回的数量不等于可用的总数量// 3id是ID类型字段GraphQL 内置的唯一标识类型序列化/反序列化方式与String相同。生成后 schema 中的Feed类型为type Feed { count: Int! id: ID links: [Link!]! }5.3 调整 feed 查询签名与 resolverexport const LinkQuery extendType({ type: Query, definition(t) { t.nonNull.field(feed, { // 1 type: Feed, args: { filter: stringArg(), skip: intArg(), take: intArg(), orderBy: arg({ type: list(nonNull(LinkOrderByInput)) }), }, async resolve(parent, args, context) { const where args.filter ? { OR: [ { description: { contains: args.filter } }, { url: { contains: args.filter } }, ], } : {}; const links await context.prisma.link.findMany({ where, skip: args?.skip as number | undefined, take: args?.take as number | undefined, orderBy: args?.orderBy as | Prisma.EnumerablePrisma.LinkOrderByWithRelationInput | undefined, }); const count await context.prisma.link.count({ where }); // 2 const id main-feed:${JSON.stringify(args)}; // 3 return { // 4 links, count, id, }; }, }); }, });变更点逐项说明// 1feed查询的返回类型更新为单个非空的Feed实例注意这里去掉了list由t.nonNull.list.nonNull.field变为t.nonNull.field// 2使用 Prisma 的count APIprisma.link.count({ where })返回匹配当前过滤条件的记录数。skip、take、orderBy对计算数量没有意义因此这条查询里省略了它们——但where必须保留保证 count 与 links 使用同一套过滤条件// 3通过把查询入参序列化后拼到标识符后缀上为 feed 查询生成唯一的idmain-feed:${JSON.stringify(args)}。这样不同的参数组合不同过滤/分页/排序总会得到不同的、唯一的标识符对基于id做缓存或订阅的前端缓存层如 Apollo Client 的缓存键十分友好// 4resolve函数返回的对象已更新为与Feed类型签名一致{ links, count, id }。注意 resolver 现在必须声明为async因为内部有两次awaitfindMany与count。5.4 验证count 与返回数量不一致是正常的用如下查询测试更新后的feedquery { feed (take: 1) { count links { id createdAt description } } }返回类似{ data: { feed: { count: 2, links: [ { id: 1, createdAt: 2021-12-14T23:21:52.620Z, description: Code-First GraphQL schemas for JavaScript/TypeScript } ] } } }注意count与返回的 links 数量并不相等take限制了_返回_的链接数量但不影响count后者反映的是数据库中_可用_匹配过滤条件的链接总数。这正是前端分页器需要的语义——“这一页取了 1 条但一共有 2 条”。六、小结与在本教程中的位置这一章完成了 HackerNews API 中“健壮列表查询”的最后一块拼图。回顾feed查询的完整演进路径每一步对应本系列的一个章节硬编码的内存数组一个简单的查询接入 Prisma 的link.findMany()连接服务器与数据库本章filterOR 子串匹配where→skip/takeLimit-Offset 分页→orderBy多字段、多方向排序→ 重构为返回{ links, count, id }的Feed对象。几个值得记住的工程要点可选参数与 Prisma 的 undefined 语义所有列表参数filter、skip、take、orderBy都是可选的透传缺失值时统一使用undefined“忽略该选项”而不是null一个具体值类型断言的原因Nexus 生成的参数类型带| null分支与 Prisma 期望的... | undefined不兼容故需as断言收窄count 与分页解耦count查询只带where不带skip/take/orderBy保证统计的是过滤后全量数据参数化 feed idmain-feed:${JSON.stringify(args)}使不同参数组合拥有稳定且唯一的标识符便于前端缓存区分。在本教程仓库中该后端能力与前端教程直接对接React Apollo 教程的入门章节 中展示的接口签名即为本章的最终形态——feed(filter: String, skip: Int, take: Int, orderBy: LinkOrderByInput): Feed!前端使用feed(skip: 0, take: 10)这类查询消费过滤、分页与排序能力。后续章节部署 与系列总结将把这套 API 部署上线并回顾整个 TypeScript Apollo Prisma 的技术栈。适用前提说明本篇基于教程中hackernews-typescript示例项目依赖nexus^1.1.0、apollo-server^3.x、prisma^3.5.0、SQLite 数据源模型Link含id、createdAt、description、url字段数据模型定义见 添加数据库章节 中的schema.prisma。示例配套源码仓库未包含在本内容仓库中文中所有src/graphql/Link.ts路径均相对于该示例项目根目录迁移到其他项目时字段名与模型需按自身schema.prisma调整。赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐Prisma API 查询Queries完全指南对象查询、Connection 分页与过滤排序实战Prisma API 查询Queries完全指南对象查询、Connection 分页与过滤排序实战 本指南以 Prisma 1.x 服务端 API 为对象后端数据库GraphQLPrisma GraphQL API 查询Queries权威指南对象查询、Connection、过滤与分页实战Prisma GraphQL API 查询Queries权威指南对象查询、Connection、过滤与分页实战 Prisma API 是 Prisma 服后端数据库GraphQLPrisma 1 GraphQL API 查询指南Object Queries、Connection Queries 与过滤/排序/分页全解析Prisma 1 GraphQL API 查询指南Object Queries、Connection Queries 与过滤/排序/分页全解析 导读 本文基于后端数据库GraphQL上一篇Flutter-Action 社区贡献指南如何参与项目开发和维护下一篇PyWebIO持续集成自动化部署Web应用的现代开发流程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表