Error on subscription GraphQL Server

Error on subscription GraphQL Server

I’m fairly new to GraphQl and I followed the GraphQL NodeJS backend tutorial here: GraphQL Realtime Subscriptions. Everything worked well and I completed the tutorial.
Next I followed the VueJS with Apollo frontend tutorial here: Vue & Apollo Subscriptions. I chose to use my own created GraphQL server from the first tutorial instead of using GraphCool, which is used in the frontend tutorial. This is the part where I’m stuck.
If I call
subscription {
Link(filter: {
mutation_in: [CREATED]
}) {
node {
id
url
description
}
}
}

in the GraphiQL client I do get the id, url and description when I create a new link in a different browser tab with the data
data: Link: node: {id: “59ef0bbeef32bb05692ee4b4”, url: “http://new-test-url2.com”, description: “Test description2”} (This subscription call is also made in the backend tutorial). But when I call
subscription {
Link(filter: {
mutation_in: [CREATED]
}) {
node {
id
url
description
postedBy {
id
name
}
votes {
id
user {
id
}
}
}
}
}

I get the error:
{message: “Cannot destructure property `Users` of ‘undefined’ or ‘null’.”,…}
{message: “Cannot destructure property `Votes` of ‘undefined’ or ‘null’.”,…}

with the data data:{Link: {node: null}}
I just can’t find the answer to this problem. Hopefully someone can help me this this.
This is my code:
schema/index.js
type Link {
id: ID!
url: String!
description: String!
postedBy: User
votes: [Vote!]!
}

type User {
id: ID!
name: String!
email: String!
password: String!
votes: [Vote!]!
}

type Vote {
id: ID!
user: User!
link: Link!
}

type SigninPayload {
token: String
user: User
}

type Query {
allLinks(filter: LinkFilter, skip: Int, first: Int): [Link!]!
}

type Mutation {
createLink(url: String!, description: String!): Link

createVote(linkId: ID!): Vote

createUser(name: String!, email: String!, password: String!): User

Related:  How to parse GraphQL request string into an object

signinUser(email: String!, password: String!): SigninPayload!
}

type Subscription {
Link(filter: LinkSubscriptionFilter): LinkSubscriptionPayload
Vote(filter: VoteSubscriptionFilter): VoteSubscriptionPayload
}

input LinkSubscriptionFilter {
mutation_in: [_ModelMutationType!]
}

input VoteSubscriptionFilter {
mutation_in: [_ModelMutationType!]
}

type LinkSubscriptionPayload {
mutation: _ModelMutationType!
node: Link
}

type VoteSubscriptionPayload {
mutation: _ModelMutationType!
node: Vote
}

input LinkFilter {
OR: [LinkFilter!]
description_contains: String
url_contains: String
}

enum _ModelMutationType {
CREATED
UPDATED
DELETED
}

schema/resolvers.js:
Query: {
allLinks: async (root, {filter, first, skip}, {mongo: {Links, Users}}) => {
let query = filter ? {$or: buildFilters(filter)} : {};
const cursor = Links.find(query);
if (first) {
cursor.limit(first);
}

if (skip) {
cursor.skip(skip);
}

return cursor.toArray();
},
},

Mutation: {
createLink: async (root, data, {mongo: {Links}, user}) => {

assertValidLink(data);
const newLink = Object.assign({postedById: user && user._id}, data);
const response = await Links.insert(newLink);

newLink.id = response.insertedIds[0];

pubsub.publish(‘Link’, {Link: {mutation: ‘CREATED’, node: newLink}});

return newLink;
},

createUser: async (root, data, {mongo: {Users}}) => {
const newUser = {
name: data.name,
email: data.email,
password: data.password,
};

const response = await Users.insert(newUser);

return Object.assign({id: response.insertedIds[0]}, newUser);
},

createVote: async (root, data, {mongo: {Votes}, user}) => {
const newVote = {
userId: user && user._id,
linkId: new ObjectID(data.linkId),
};

const response = await Votes.insert(newVote);

return Object.assign({id: response.insertedIds[0]}, newVote);
},

signinUser: async (root, data, {mongo: {Users}}) => {
const user = await Users.findOne({email: data.email});
if (data.password === user.password) {
return { token: `token-${user.email}`, user };
}
},
},

Subscription: {
Link: {
subscribe: () => pubsub.asyncIterator(‘Link’),
},
},

Link: {
id: root => root._id || root.id,

// postedBy: async ({postedById}, data, {dataloaders: {userLoader}}) => {
// return await userLoader.load(postedById);
// },

postedBy: async ({postedById}, data, {mongo: {Users}}) => {
return await Users.findOne({_id: postedById});
},

votes: async ({_id}, data, {mongo: {Votes}}) => {
return await Votes.find({linkId: _id}).toArray();
},
},

Solutions/Answers:

Solution 1:

Hoping you were able to solve this one since it has been so long, I just figured out this one today myself. The issue here is that the subscription server was never passed in the context, ie the dataloaders/user information.

Related:  HATEOAS vs GraphQL decision criteria set for microservices?

In this lesson(https://www.howtographql.com/graphql-js/4-connectors/) you setup the context for the express server, but this was never added to the subscription server:

  const buildOptions = async (req,res) => {                                                     
const user = await authenticate(req, mongo.Users)                                           
return {                                                                                    
  context: {                                                                                
    dataloaders: buildDataloaders(mongo),                                                   
    mongo,                                                                                  
    user                                                                                    
  },                                                                                        
  formatError,                                                                              
  schema,                                                                                   
}                                                                                           

In order to add this to the subscription server I used these options myself:

const subscriptionBuildOptions = async (connectionParams,webSocket) => 
{                      
  return {                                                                                   
    dataloaders: buildDataloaders(mongo),                                                    
    mongo,                                                                                   
  }
}  

I then added this to the SubscriptionServer by simply adding the onConnect Parameter:

const server = createServer(app);                                                             
server.listen(PORT, () => {                                                                   
  SubscriptionServer.create(                                                                  
    {execute, subscribe, schema, onConnect: subscriptionBuildOptions},                        
    {server, path: '/subscriptions'},                                                         
  );                                                                                          
  console.log(`Hackernews GraphQL server running on port ${PORT}.`)                           
}); 

And that should mostly get everything working. I personally am doing the reactjs tutorial, so not sure what individual issues there will be for that, but the reactjs tutorial api calls were a bit out of sync throughout.

References