graphqlapolloprismaresolverprisma2

Apollo and Prisma Graphql explicit model relationship not queryable


I have begun testing out prisma 2 and graphql in general for a new application. I am running into an issue with an explicit many to many table on being able to query relations.

Here is my apollo schema:

scalar DateTime

type Query {
    user(id: String!): User
    users: [User]
    spaces: [Space]
    roles: [Role]
}

type Mutation {
    createUser(id: String!, email: String!): User!
    createSpace(name: String!): Space!
}

type User {
    id: ID!
    email: String!
    spaces: [UserSpace!]
    createdAt: DateTime!
    updatedAt: DateTime!
}

type Space {
    id: ID!
    name: String!
    users: [UserSpace!]
    createdAt: DateTime!
    updatedAt: DateTime!
}

type Role {
    id: ID!
    name: String!
    description: String!
    users: UserSpace
    createdAt: DateTime!
    updatedAt: DateTime!
}

type UserSpace {
    id: ID!
    user: User!
    space: Space!
    role: Role!
    createdAt: DateTime!
    updatedAt: DateTime!
}

Here is my prisma schema:

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// npx prisma migrate dev
// npx prisma generate

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model User {
  id String @id
  email String @unique
  spaces UserSpace[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Space {
  id Int @default(autoincrement()) @id
  name String @unique
  users UserSpace[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Role {
  id Int @default(autoincrement()) @id
  name String @unique
  description String
  users UserSpace[]
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model UserSpace {
  id Int @default(autoincrement()) @id
  user User @relation(fields: [userId], references: [id])
  userId String
  space Space @relation(fields: [spaceId], references: [id])
  spaceId Int
  role Role @relation(fields: [roleId], references: [id])
  roleId Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

Here is my mutations resolver:

const { prisma } = require(".prisma/client");

async function createUser(parent, args, context, info) {
    return await context.prisma.user.create({
        data: {
            ...args,
        },
    });
}

async function createSpace(parent, args, context, info) {
    const isAuthenticated = context.authentication.isAuthenticated;
    let role = null;

    if(!isAuthenticated) {
        throw new Error("Not Authenticated");
    }

    try {
        role = await context.prisma.role.findUnique({
            where: {
                name: "Space Administrator",
            },
        });
    }
    catch(err) {
        throw new Error(err);
    }

    return await context.prisma.space.create({
        data: {
            ...args,
            users: {
                create: {
                    role: { 
                        connect: { id: role.id },
                    },
                    user: {
                        connect: { id: context.authentication.auth0Id },
                    },
                },
            },
        },
    });
}

module.exports = {
    createUser,
    createSpace,
}

Here is my user resolver (I know this is where the problem is however I do not know how to solve the issue):

function spaces(parent, args, context, info) {
    return context.prisma.user.findUnique({ where: { id: parent.id } }).spaces();
}

module.exports = {
    spaces,
}

Basically when I create the space the user is added as a Space Administrator to the space and then should be able to be queried with the following:

query {
  users {
    id
    email
    spaces {
      id
      role {
        name
      }
      space {
        name
      }
    }
    createdAt
  }
}

However when I run the query I get the following error:

"message": "Cannot return null for non-nullable field UserSpace.role.",

How in prisma 2 do I make the resolver for the users work with an explicit many to many table and how it has the third relation in there? I am new to prisma and graphql so if there anything else that stands out also I would like to have the input.


Solution

  • I'm using the word type to refer to object-models in your GraphQL schema and model to refer to data-models in your Prisma Schema.

    The Problem

    I see that you have a User type resolver, that has a resolver function for User.spaces field in your User type. The query that you have defined in your User.spaces resolver will return the relevant userSpace records from the database.

    However, these userSpace records do not by default resolve the role field, as it is a relation field. This is how prisma works (relation fields are not resolved by default, unless explicitly stated).

    Solution

    Create a resolver for the UserSpace type and explicitly define the the resolver function for UserSpace.role field. This is what it will look like

    // UserSpace resolver module
    
    function role(parent, args, context, info) {
        return context.prisma.userSpace.findUnique({ where: { id: parent.id } }).role();
    }
    
    module.exports = {
        role,
    }
    
    

    While there are some other ways to solve this problem, the way I have shown (along with the specific syntax) is recommended because under the hood it allows prisma to perform certain optimizations to solve the n+1 query problem. But, if you don't know what that is, you don't necessarily need to worry about it either.