DEV Community

Cover image for GraphQL subscriptions with Node.js
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

GraphQL subscriptions with Node.js

Written by Deepak Gupta✏️

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.

High-Level Overview of GraphQL Subscriptions

(Source: GitHub)

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.

Diagram Illustrating the Query Principle in the Context of GraphQL subscriptions

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.

LogRocket Free Trial Banner

What we will code?

We will 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, 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
Enter fullscreen mode Exit fullscreen mode

Next, install the dependency required.

npm i --s graphql-yoga
Enter fullscreen mode Exit fullscreen mode

Now we’ll create all our files.

touch index.js postData.json typeDefs.js resolver.js
Enter fullscreen mode Exit fullscreen mode
  • 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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
  });
}
Enter fullscreen mode Exit fullscreen mode

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!');
}
Enter fullscreen mode Exit fullscreen mode

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!');
    }
Enter fullscreen mode Exit fullscreen mode

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;
},
Enter fullscreen mode Exit fullscreen mode

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');
  }
}
Enter fullscreen mode Exit fullscreen mode

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.`,
  )
})
Enter fullscreen mode Exit fullscreen mode

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"
},
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.

Running a getPosts Query in GraphQL

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,
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Add GraphQL Subscriptions to Channel Post

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
  } 
}
Enter fullscreen mode Exit fullscreen mode

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

Response Stream Data in a GraphQL Subscription

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!
}
Enter fullscreen mode Exit fullscreen mode

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
}
})
Enter fullscreen mode Exit fullscreen mode

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');
}
}
Enter fullscreen mode Exit fullscreen mode

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

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

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.


200's only ‎✅: Monitor failed and show GraphQL requests in production

While GraphQL has some features for debugging requests and responses, making sure GraphQL reliably serves resources to your production app is where things get tougher. If you’re interested in ensuring network requests to the backend or third party services are successful, try LogRocket.

Alt Text

LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on problematic GraphQL requests to quickly understand the root cause. In addition, you can track Apollo client state and inspect GraphQL queries' key-value pairs.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. Start monitoring for free.


The post GraphQL subscriptions with Node.js appeared first on LogRocket Blog.

Top comments (0)