graphqlprismaapollo-server

Issue querying related data in Apollo Server 4 with Prisma Schema


I am using Apollo Server 4 and Prisma 5 to build a simple ecomm project. At the moment I am trying to set up my resolvers to be able to bidirectionally query for Users + their comments...and also query Comments as the top level and return it's associated user. In the database Comment table has an associated user_id FK relationship to the User table. User table does NOT have a comments id, thus a one -> many and many -> one relationship.

When I try to query a user, comments is null unless I write a separate resolver for it and then use Prisma orm to include the relationship. I don't want to follow this as it seems like an antipattern when using gql. What I want to accomplish is to have a userByEmail() resolver in my top level Query{} from my SDL...and then have a Comment: {} 2nd level object in my resolvers to then have the corresponding User be the parent argument coming in the resolver...this is more gql dependant than db dependant.

Relevant Prisma Models:

model User {
  id          String        @id @default(uuid())
  username    String        @unique
  email       String        @unique
  password    String
  first_name  String
  last_name   String?
  created_at  DateTime      @default(now())
  updated_at  DateTime      @updatedAt
  deleted     Boolean       @default(false)
  comments    Comment[]
  ratings     Rating[]
  orders      Order[]
  UserAddress UserAddress[]
}


model Comment {
  id         String        @id @default(uuid())
  user       User     @relation(fields: [user_id], references: [id])
  user_id    String   // Change type to String
  product    Product  @relation(fields: [product_id], references: [id])
  product_id String
  content    String
  created_at DateTime @default(now())
  updated_at DateTime @updatedAt
  deleted    Boolean  @default(false)
}

Relevant schema.graphql types:

type Query {
  "Get a user by their email"
  userByEmail(email: String): User!
}

"This type represents a user in the system"
type User {
  "Unique identifier for the user"
  id: ID!

  "Username for login"
  username: String!

  "User's email address"
  email: String!

  "User's first name"
  first_name: String!

  "User's last name (optional)"
  last_name: String!

  "List of comments authored by the user (optional)"
  comments: [Comment]

  "List of ratings created by the user (optional)"
  ratings: [Rating]

  "List of orders placed by the user (optional)"
  orders: [Order]

  "List of user's addresses"
  addresses: [Address]
}

type Comment {
  "Unique identifier for the comment"
  id: ID!

  "Content of the comment"
  content: String!

  "User who authored the comment"
  user: User

  "Product this comment is associated with"
  product: Product
}

resolvers.ts

import { getUserByEmail, getCommentsByUserId } from '../../dataAccessLayer/orm/user';
import { User } from '../../generated/types';

// Use intersection type to combine Resolvers and Query
export const resolvers = {
  Query: {
    userByEmail: async (_parent: unknown, { email }: { email: string }) => {
      console.log('parent', email);
      const user = await getUserByEmail(email); // Already includes comments
      return user;
    },
  },
  Comment: {
    userComments: (parent: User) => {
      console.log('parent', parent);
      const userId = parent.id;
      return getCommentsByUserId(userId);
    },
  },
};

ORM DB Queries

import prisma from '../prismaClient';

export async function getUserByEmail(email: string) {
  return await prisma.user.findUnique({
    where: {
      email,
    },
  });
}

export async function getCommentsByUserId(id: string) {
  return await prisma.comment.findMany({
    where: {
      user_id: id,
    },
    include: {
      user: true, // Include user relation
    },
  });
}

When I run my project I get this error:

 Error: Comment.userComments defined in resolvers, but not in schema
[0]     at addResolversToSchema

How can I properly query for sub fields using the Apollo Recommended approach of using (parent, args, context, info) arguments where parent should be the related user initially queried for when trying to get comments? This way I don't stitch together db queries at the orm level.

Thanks!


Solution

  • Take a look at the examples in apollo server doc, on how to implement resolvers

    In the schema you have already included comments property to the User type

    type User {
      id: ID!
      username: String!
    
      "List of comments authored by the user (optional)"
      comments: [Comment]
    
      # ... other fields
    }
    
    

    you also defined userByEmail to fetch user in top-level type Query

    type Query {
      userByEmail(email: String): User!
    }
    

    Next, you need to implement resolvers for this schema. For the type Query you need to implement resolver for the method userByEmail. You've done this part

    import { getUserByEmail } from '../../dataAccessLayer/orm/user';
    import { User } from '../../generated/types';
    
    // Use intersection type to combine Resolvers and Query
    export const resolvers = {
      Query: {
        userByEmail: async (_, { email }: { email: string }) => {
          console.log('parent', email);
          const user = await getUserByEmail(email); // Already includes comments
          return user;
        },
      },
    };
    

    The next thing you need to do, is to define resolver for comments property of User, which give you exactly what you want: user comments.

    import { getUserByEmail, getCommentsByUserId } from '../../dataAccessLayer/orm/user';
    import { User } from '../../generated/types';
    
    // Use intersection type to combine Resolvers and Query
    export const resolvers = {
      Query: {
        userByEmail: async (_, { email }: { email: string }) => {
          const user = await getUserByEmail(email); // Already includes comments
          return user;
        },
      },
      // User. not Comment. 
      User: {
        // The parent entity for comments field is user
        comments: async (parent: User, _: any) {
          if (parent.comments !== undefined) {
             // you mentioned that user fetched with userByEmail already 
             // includes comments, so you might not even need to refetch it
             return parent.comments 
          } 
          const comments = await getCommentsByUserId(user.id)
          return comments;
        },
      }
    };
    

    And this is an example of the query compatible with our schema

    query userWithComments($email: String!) {
      userByEmail(email: $email) {
        id
        username
        # some other fields
        comments {
          id
          content   
        }
      }
    }