Apollo Federation Resolve Array from IDs

2.2k views Asked by At

I am using MongoDB as the database for my federated Apollo GraphQL API.

(My code is in TypeScript)

I have created a __resolveReference method for the Post resolvers object, and this works fine when I call it from the user service as follows:

User: {
...
    firstPost(user: any) {
            return { __typename: "Post", _id: user.firstPost };
        },
     }
...
}

However, when I call it on an array, it does not work. The following is how I call it:

User: {
...
      posts(user: any) {
            user.posts.map((x: any) => {
                return { __typename: "Post", _id: x };
            });
      },
...
}

The post __resolveReference code follows:

Post: {
...
async __resolveReference(reference: any, { db }: any) {
            console.log(reference);
            console.log(reference._id);
            try {
                let r = (await db
                    .collection("posts")
                    .findOne({ _id: new ObjectID(reference._id) })) as Post;

                console.log(r.content);

                return r;
            } catch (err) {
                console.log(err.stack);
            }
        },
...

I know that the __resolveReference is not being "hit" when called from the posts resolver on the User object because the console.log()s are not shown in the Terminal, again, only from the posts resolver and not firstPost.

I would like some help in getting the __resolveReference working for arrays.

Thank you.

3

There are 3 answers

0
the stack goat On

Using an array to resolve reference should work.

If you create a new list instead of changing a resolver input, this snippet of code will work.

User: {
...
  posts(user: any) {
        let postlist = [];
        user.posts.forEach(postId => postlist .push({ __typename: "Post", _id: postId }));
        return postlist;
}      
...
} 

Hope this helps!

1
devkmt On

In Apollo Federation return array in __resolveReference doesn't work properly if you want to return a array a solution is create a new type. For example, if you have User implemented in one service and post in another a simple representation of User and Post Model are something like this:

typescript
-- User Service
    type User{
        id: ID!
        email: String!
        password: String!
    }

-- Post Service
    type Post{
        id: ID!
        content: String!
        postedBy: ID!
        date: Date
        comments: [String]!
    } 

Add a reference to Post type

    //define a key in Post
    type Post @key(fields:"id") 
    
    //Add a field on User like
    type User{
        id: ID!
        email: String!
        password: String!
        post: [Post]!
    }
    
    extend type Post @key(fields:"id"){
        id: ID! @external
    }

This method doesn't work. A solution is to create a new type in Post Service:

    type PostList @key(fields: "idUser"){
        idUser: ID!
        posts: [Post]!
    }
    
    PostList : {
        __resolveReference(reference){
            //data is a array of post
            let userPost = data.filter((p)=>{p.postedBy==reference.idUser})
            return {idUser: reference.idUser, posts: userPost}
        }
    }

And in your User service do this:

    type User{
        id: ID!
        email: String!
        password: String!
        posts: PostList
    }
    
    extend type PostList @key(fields:"idUser"){
        idUser: ID! @external
    }
    
    in User resolver
    User : {
        posts(_parent){
            return {__typename: "PostList", idUser: _parent.id}
        }
    }

And see if __resolveReference work with async/await functions.

0
Michael On

Another way to handle this would be to extend the User type on the Post service.

I am using prisma in this example, You can resolve your data using any fetch function, but the practice is the same.

Post Service

const resolvers = {
  User: {
    posts: async (root: any) => await prisma.post.findMany({ where: { userId: root.id } }),
  },
};

const typeDefs = gql`
  type Post {
    id: ID!
  }

  extend type User @key(fields: "id") {
    id: ID! @external
    posts: [Post]!
  }
`

User Service

const resolvers = {
  Query: {
    user: async (_root: any, args: any) => await prisma.user.findUnique({ where: { id: args.id } })
  },
  User: {
    __resolveReference: async (root: any) => await prisma.user.findUnique({ where: { id: root.id } }),
  },
};

const typeDefs = gql`
  type Query {
    user(id: ID!): User
  }

  type User @key(fields: "id") {
    id: ID!
  }
`