GraphQL Subscription With Node.js

Email

Ever wonder how Facebook notifies you when a friend posts something? Or how Google Maps updates your location in real time? The answer to these and many other modern mysteries is (among other things) GraphQL subscriptions.

In this article, we’ll provide a basic understanding of GraphQL subscriptions for events on JSON data in a Node.js server.

Before we get started, you should have:

  • Node.js installed

  • A basic understanding of GraphQL concepts such as schema, query, mutation, and resolve

Without further ado, let’s dive in.

What are GraphQL subscriptions?

GraphQL subscriptions enable you to subscribe to events under a source stream and receive notifications in real-time via a response stream when a selected event executes. Once a GraphQL subscription is executed, a persistent function is created on the server that maps an underlying source stream to a returned response stream.

GraphQL subscriptions differ from queries in the way the data is delivered to the client. The latter immediately returns a single response, while the former returns a result every time data is published on a topic to which you have subscribed.

This is facilitated by a publisher/subscriber mechanism that can handle event-driven systems efficiently and at a scale. In a publisher/subscriber model, all messages and data flow according to the queue principle (first in, first out) and then to the subscriber.

Note: for production, it is recommended to use the pub/sub implementation of Redis.

There are many packages available on npm that can be used to implement the pub/sub model for GraphQL subscriptions. Below are some of the most commonly used packages.

  • graphql-yoga is a fully-featured GraphQL server with focus on easy setup, performance, and a great developer experience

  • graphql-subscriptions lets you wire up GraphQL with a pub/sub system (such as Redis) to implement subscriptions in GraphQL

  • apollo-server-express is the express and connect integration of GraphQL server. Apollo server is a community-maintained, open-source GraphQL server that works with many Node.js HTTP server frameworks

We will use the graphql-yoga module because it is built over the other two and provides all necessary dependency and server binding with Node.js under the hood. Don’t worry about those last two things; once you get a hang of the implementation, they will be a breeze.

What we will code

We’ll use the post data that is stored inside a JSON file, and we’ll perform the following operations.

  • getPosts (read all posts)

  • getPost (read a specific post by ID)

  • updatePost (update a post)

  • deletePost (delete a post)

  • createPost (create a post)

Then, we’ll add the subscription to the last three operations.

Now it’s time to get our hands dirty with some code.

First, make a folder, name it whatever you like, and initialize it using Node.js.

mkdir graphql-sub
cd graphql-sub
npm init

Next, install the dependency required.

npm i --s graphql-yoga

Now we’ll create all our files.

touch index.js postData.json typeDefs.js resolver.js
  • index.js is responsible for the GraphQLServer creation with pub/sub, which we will see in a minute

  • postData.json is the JSON file on which we will perform CRUD. Add the following code or an array of an object for a post who’s schema should be:

    • id:ID!

    • title:String!

    • subtitle:String!

    • body:String!

    • published:Boolean!

    • author: String!

    • upvotes: Int!

    • downvotes: Int!

    • commentCount: Int!

  • typeDefs.js will be used to create schemas for the above operations

  • resolvers.js will have the logic to resolve for all queries, mutation, and subscriptions defined under typeDefs.js

Inside typeDefs.js, add the following code.

//type definitions and schemas - (operation and data structure)
const typeDefs = `
    type Query {
        getPosts(query: String):[Post!]!
        getPost(query: String):Post!
    }
    type Post{
        id:ID!
        title:String!
        subtitle:String!
        body:String!
        published:Boolean!
        author: String!
        upvotes: Int!
        downvotes: Int!
        commentCount: Int!
    }
    type Mutation{
        updatePost(
          id:ID!
          title:String!
          subtitle:String!
          body:String!
          published:Boolean!
          author: String!
          upvotes: Int!
          downvotes: Int!
          commentCount: Int!
        ): Post!
        deletePost(id: ID!): Post!
        createPost(
          id:ID!
          title:String!
          subtitle:String!
          body:String!
          published:Boolean!
          author: String!
          upvotes: Int!
          downvotes: Int!
          commentCount: Int!
        ): Post!
    }
    type Subscription {
        post: SubscriptionPayload!
    }
    
    type SubscriptionPayload {
        mutation: String!
        data: Post!
    }
`;
module.exports = typeDefs;

Other than the normal schema definitions for queries and mutation, we have a type called Subscription that is added on the post object via a custom type SubscriptionPayload.

Therefore, each time a change is made to a post object, an event will be triggered for all who subscribe to events that return the name of the mutation performed — update, delete, and create and post data.

Now let’s code our resolvers.js for the above typeDefs.

const posts = require('./postData');
//Resolvers - This are the set of the function defined to get the desired output for the given API
const resolvers = {
  Query:{
    
  },
  
  Mutation:{
   
  },
  
Subscription:{
    
  },
}
module.exports = resolvers;

Coding objects

We first imported the postData and then added our resolver object, which contains our query, mutation, and subscription object.

Let’s code each object one by one .

Query object

We will define two queries — getPost and getPosts — inside our query object.

// return all posts
getPosts() {
  return posts;
},
// return post by args passed, for now it just check for body and 
// title for the post
getPost(parent, args){
  return posts.filter((post) => {
    const body =  post.body.toLowerCase().includes(args.query.toLowerCase())
    const title =  post.title.toLowerCase().includes(args.query.toLowerCase())
    return body || title;
  });
}

Mutation object

We will define three mutations — createPost ,updatePost, and deletePost — inside our mutation object.

createPost

Check whether the post for the ID already exists. If yes, we’ll throw an error to GraphQL server. Otherwise, we’ll create the post from args and add it to our posts JSON data.

createPost(parent, args, { pubsub }) {
  const id = parseInt(args.id, 10);
  const postIndex = posts.findIndex((post)=> post.id === id);
  if(postIndex === -1) {
    posts.push({
      ...args
    });
    
    pubsub.publish('post', {
      post:{
          mutation: 'CREATED',
          data: {...args}
      }
    }); 
    
   return {...args};
  };
  throw new Error('Post with same id already exist!');
}

We published an event called CREATED that will be triggered to all subscribers of the channel post through the socket and return newly created post data.

updatePost

We will check whether the post for the ID already exists. If it does, we’ll update the post with the args passed. Otherwise, it’ll throw an error.

updatePost(parent, args, { pubsub }){
  const id = parseInt(args.id, 10);
  const postIndex = posts.findIndex((post)=> post.id === id);
  if (postIndex !== -1) {
    const post = posts[postIndex];
    const updatedPost = {
      ...post,
      ...args
    };
  posts.splice(postIndex, 1, updatedPost);
  pubsub.publish('post', {
      post:{
          mutation: 'UPDATED',
          data: updatedPost
      }
    });
    return updatedPost;
  }
throw new Error('Post does not exist!');
}


As you can see, we again published a new event called UPDATED that returns the updated post data.

deletePost

We will check whether the post for the ID already exists. If it does, we’ll delete it from the posts array or throw an error.

deletePost(parent, args, { pubsub }){
  const id = parseInt(args.id, 10);
  const isPostExists = posts.findIndex((post)=> post.id === id);
  if(isPostExists === -1) {
    throw new Error('Post does not exist!');
  }
  //splice will return the index of the removed items from the array object
  const [post] = posts.splice(isPostExists, 1);
  // return post;
  pubsub.publish('post', {
    post:{
        mutation: 'DELETED',
        data: post
    }
  })
  return post;
},

Again, we published a new event called DELETED with the delete post data.

Subscription object

This object uses a pubsub.asyncIterator function to map the event underlying the source stream to a returned response stream.

The asyncIterator takes the channel name through which the event across the app will be mapped out.

post:{
  subscribe(parent, args, {pubsub}){
    return pubsub.asyncIterator('post');
  }
}

Now the only file left is the index.js. Add the following code to this file.

const { GraphQLServer, PubSub } = require('graphql-yoga');
const typeDefs = require('./typeDefs');
const resolvers = require('./resolvers');
const pubsub = new PubSub()
const server  = new GraphQLServer({
  typeDefs,
  resolvers,
  context:{
    pubsub
  }
})
const options = {
  port: 3000
};
server.start(options, ({ port }) => {
  console.log(
    `Graphql Server started, listening on port ${port} for incoming requests.`,
  )
})

Here, we created a GraphQLServer, passed all our files, and started the server.

Finally, we’ll add a script to run our project in package.json.

"scripts": {
  "start": "node index.js"
},

Open the terminal and run npm start. If everything is good, you’ll see the following message.

Graphql Server started, listening on port 3000 for incoming requests.

Now head over to the browser and type localhost:3000. You’ll see a GraphQL Playground.

Just to check that everything is working as expected, let’s run a getPosts query.

To start our subscription to the post changes, we’ll open up a new tab in GraphQL Playground and run the following.

subscription{
  post{
    mutation
    data{
      id,
      title,
      subtitle,
      body,
      published
      author,
      upvotes,
      downvotes,
      commentCount,
    }
  }
}

This enables us to add a subscription to our channel post and start listening for any event published in the channel.

To see it in action, just perform any of the mutations. For example:

mutation {
  updatePost(
    id: 8,
    downvotes:3,
    author: "deepak gupta",
    published: true,
    subtitle: "testinng subtitle",
    body: "testing body",
    commentCount: 12,
    upvotes: 4,
    title: "oh yeah :)"
  ) {
    id
  } 
}


As you can see, the post response stream gave back the data for the update event.


Recapping the GraphQL subscription process

To wrap up our tutorial, let’s quickly recap the subscription process. The subscription is defined below in typeDefs.js.

type Subscription {
  post: SubscriptionPayload !
}

type SubscriptionPayload {
  mutation: String !,
  data: Post !
}

Use the pub/sub method provided by graphql-yoga to subscribe and publish. This can also facilitate mechanisms like EventEmitter.

const {
  GraphQLServer,
  PubSub
} = require('graphql-yoga');
const pubsub = new PubSub()
const server = new GraphQLServer({
  typeDefs,
  resolvers,
  context: {
    pubsub
  }
})

Implement the resolver for subscription type to map the event using pubsub.asyncIterator. Once we request a subscription from GraphQL Playground, it will add our socket to its listening socket list and send back events while we call pubsub.publish.

post: {
  subscribe(parent, args, {
    pubsub
  }) {
    return pubsub.asyncIterator('post');
  }
}

Finally, call the pubsub.publish() method from the channel added mutation.

pubsub.publish('post', {
  post: {
    mutation: 'UPDATED',
    data: updatedPost
  }
});

If you’ve followed these steps to a T, you’ve successfully created a GraphQL subscription, a real-time method to sync up client and server.

To see the above app in action, head over to CodeSandbox.

Get yourself added to our 2500+ people subscriber family to learn and grow more and please hit the share button on this article to share with your co-workers, friends, and others.

Check out articles on Javascript, Angular, Node.js, Vue.js

For more articles stay tuned to overflowjs.com

Thank you!

Email

About Deepak Gupta

Deepak is profound programmer and financial educator in India. He has co-founded couple of startup from scratch and worked in more than 12 startup and big corporate with different roles.

Owing to his interest, he has been writing blogs regarding JavaScript and other framework to help people starting with it. Also, love to educate people about trading and cryptoworld in his free time.