'Apollo Federation Resolve Array from IDs
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.
Solution 1:[1]
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!
Solution 2:[2]
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.
Solution 3:[3]
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!
}
`
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | the stack goat |
| Solution 2 | Dmitry S. |
| Solution 3 | Michael |
