What is the right way to define explicit many-to-many relationship with Nexus / Prisma?

41 Views Asked by At

I'm pretty new to Prisma / Nexus and GraphQL in general.

I had a pretty hard time defining a Prisma explicit many-to-many relationship field resolver in Nexus but I finally found 2 different working solutions.

Could someone tell me which solution is the best and why ? Where can I find some documentation about the pros / cons / perf of both solutions ?

schema.prisma file

model User {
  id        Int               @id @default(autoincrement())
  email     String            @unique
  password  String
  groups    UsersOnGroups[]
  recipes   Recipe[]
  lists     List[]
}

model Group {
  id      Int                 @id @default(autoincrement())
  name    String   
  users   UsersOnGroups[]
  recipes Recipe[]
  lists   List[]
}

model UsersOnGroups {
  user User @relation(fields: [userId], references: [id])
  userId Int 
  group Group @relation(fields: [groupId], references: [id])
  groupId Int

  @@id([userId, groupId])
}

Only posting User because Group works basically the same. Some fields got omitted for brevity.

user.ts file -> 1st solution

export const User = objectType({
  name: 'User',
  definition(t) {
    t.nonNull.int('id')
    t.string('email')
    t.nonNull.list.nonNull.field('groups', {
      type: Group,
      resolve: async (parent, _args, context) => {
        const groups = await context.prisma.usersOnGroups.findMany({
          where: {
            userId: parent.id,
          },
          include: {
            group: true,
          },
        })

        return groups.map((groupRelation) => groupRelation.group)
      },
    })
  },
})

user.ts file -> 2nd solution

export const User = objectType({
  name: 'User',
  definition(t) {
    t.nonNull.int('id')
    t.string('email')
    t.nonNull.list.nonNull.field('groups', {
      type: Group,
      resolve: (parent, _args, context) => {
        return context.prisma.group.findMany({
          where: {
            users: {
              every: {
                userId: parent.id,
              },
            },
          },
        })
      },
    })
  },
})

Tried both solution and both return the same and correct values but I don't have enough data to test out the performances.

EDIT : Considering the N+1 issue, I came out with this other solution that's probably better.

t.nonNull.list.nonNull.field('groups', {
      type: Group,
      resolve: async (parent, _args, context: Context) => {
        const groups = await context.prisma.user
          .findUnique({
            where: {
              id: parent.id,
            },
          })
          .groups({
            include: {
              group: true,
            },
          })

        return groups?.map((groupRelation) => groupRelation.group) ?? []
      },
    })
1

There are 1 best solutions below

1
On

I don't know which one is better, but you can test it out yourself with prisma info logs

//api - index.ts
export const prisma = new PrismaClient({
   log: [
     "info",
     {
       emit: "event",
       level: "query",
     },
   ],
});

 prisma.$on("query", (e) => {
   console.log("______________");
   console.log("Query: " + e.query);
   console.log("Params: " + e.params);
   console.log("Duration: " + e.duration + "ms");
 });

//... other code

You can check the duration of each query, and the number of queries, and compare them.

However, your biggest performance issue will be with the n+1 problem for both solutions: n+1 issue

If you cannot solve it with prisma, you can solve it with a dataloader