简体   繁体   中英

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

    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();
    },
},

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM