如何避免GraphQL解析器中多对多关系的无限递归问题?
多对多关系下GraphQL解析器的类型递归问题解决办法
问题背景
你定义了Player与Match的多对多关系,使用graphql-codegen生成TypeScript类型后,编写Query.players和Query.matches解析器时遇到矛盾:
- 直接用
prisma.player.findMany()返回的对象缺少matches字段,不符合生成的Player类型要求(报错Property 'matches' is missing...) - 如果强制关联查询
matches,又会触发Match关联的players,陷入无限递归查询
你的schema定义如下:
schema.graphql
scalar Score type Player { id: ID! name: String! rating: Int! matches: [Match!]! } enum MatchStatus { ONGOING COMPLETED CANCELLED } type Match { id: ID! players: [Player!]! score: Score! status: MatchStatus! } type Query { players: [Player!]! player(id: ID!): Player matches: [Match!]! match(id: ID!): Match }
schema.prisma
model Player { id String @id @default(cuid()) name String rating Int matches Match[] } model Match { id String @id @default(cuid()) score String status String players Player[] }
解决方法
方法1:按需加载关联字段(推荐)
利用GraphQL的分层解析特性,根查询只返回基础字段,关联字段交给对应类型的解析器懒加载,同时用类型断言绕过TS的严格检查:
import { Resolvers } from './generated/graphql'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); export const resolvers: Resolvers = { Query: { // 根查询仅返回Player基础字段,matches交给Player类型的解析器处理 players: async () => { return prisma.player.findMany() as unknown as Resolvers['Query']['players']['returnType']; }, matches: async () => { return prisma.match.findMany() as unknown as Resolvers['Query']['matches']['returnType']; }, // ...其他查询解析器 }, Player: { // 单独处理Player的matches字段,按需查询关联数据 matches: async (parent) => { return prisma.player.findUnique({ where: { id: parent.id } }).matches(); }, }, Match: { // 单独处理Match的players字段,按需查询关联数据 players: async (parent) => { return prisma.match.findUnique({ where: { id: parent.id } }).players(); }, }, };
这种方式既避免了一次性查询所有关联数据的性能问题,也解决了类型不匹配问题,符合GraphQL的设计理念。
方法2:调整GraphQL Schema的字段可选性
如果业务允许关联字段为可选,可去掉关联字段的!标记,让生成的TS类型允许这些字段缺失:
type Player { id: ID! name: String! rating: Int! matches: [Match] # 去掉!,允许为null或空数组 } type Match { id: ID! players: [Player] # 去掉!,允许为null或空数组 score: Score! status: MatchStatus! }
修改后,prisma.player.findMany()返回的对象会直接符合生成的Player类型,无需额外处理。
方法3:配置GraphQL Codegen映射类型
通过codegen的mappers配置,将GraphQL类型映射为Prisma返回的基础字段类型,让生成的解析器类型自动匹配实际返回的数据:
在你的codegen配置文件(如codegen.ts)中添加如下配置:
import type { Prisma } from '@prisma/client'; export default { schema: './schema.graphql', generates: { './src/generated/graphql.ts': { plugins: ['typescript', 'typescript-resolvers'], config: { // 将GraphQL的Player类型映射为只包含基础字段的Prisma类型 mappers: { Player: Prisma.PlayerGetPayload<{ select: { id: true, name: true, rating: true } }>, Match: Prisma.MatchGetPayload<{ select: { id: true, score: true, status: true } }>, }, }, }, }, };
重新生成类型后,根查询解析器返回的Prisma基础字段对象会完全符合类型要求,无需类型断言。
内容的提问来源于stack exchange,提问作者marvincyk
相关产品推荐
相关产品推荐

