close icon
daily.dev platform

Discover more from daily.dev

Personalized news feed, dev communities and search, much better than what’s out there. Maybe ;)

Start reading - Free forever
Continue reading >

How GraphQL Subscriptions Work: Tips, Best Practices and Examples.

How GraphQL Subscriptions Work: Tips, Best Practices and Examples.
Author
Chidume Nnamdi
Related tags on daily.dev
toc
Table of contents
arrow-down

🎯

Real-time technology has been one of the most intriguing aspects of development in modern tech. Companies leverage real-time communication to deliver products and services fast to their consumers. Real-time can be found everywhere, it is being in chat apps, analytics, progress report, sales reports, etc.

Facebook uses real-time polling tech to display the news feed. Twitter uses real-time tech to display tweets, and so on.

With these high demands or high usage of real-time tech, most techs incorporate this feature in their tool. One of them is the new API fetching tool, GraphQL.

We are going to look into GraphQL to see how it polls data real-time, but first, let's know what GraphQL is.

What is GraphQL?

GraphQL is a query language for data APIs, it is also a runtime that needs to be implemented on the backend.

The query language is queries and mutations written in text strings and they are sent to a POST endpoint.

A query is written to read from the graphql API endpoint.


query {

  foods {

    title

    type

  }

}

The above states that we want to query for food items from the server. And also we want the fields title and type to be in the food objects returned. GraphQL allows us to state the shape of the answer to be returned by the server.

The food object has other fields like created_at, chef, etc but with GraphQL we can state what fields we want present in the result. That's the ultimate power of GrpahQL.

The query is sent as a payload to the GrpahQL endpoint via POST. The GraphQL engine parses the query and executes the query against the corresponding resolver. Now, from the result from the resolver, GraphQL picks out the fields that are needed and sends them back to the user.

GraphQL uses resolvers to get the data, this data is what the GrpahQL works against the query or mutation to know the action to perform. The query or mutation has a name as the resolver name so the GraphQL engine can match them and perform the necessary action.

For e.g the above query foods will be matched against a function that has the name foods, the function will be called, the returned result is what will be returned as the result of the query.

Mutation is used to add, edit or delete data from the server.


mutation {

  newFood(title: "Coconut Rice") {

    title

    body

  }

}

The above mutation adds a new food to the server. The text string will be sent to the server as a payload. During execution, the GraphQL engine will match the newFood to the name in the functions in the Mutation object, the match is called with arguments and the returned result is sent back to the user.

The resolver is the interface between GraphQL and the server's database.

GraphQL also uses a typed schema. This type schema helps us to identify errors at both development and compile-time which results in fewer runtime bugs.

For example, our foods query will have the type schema like this:


type Food {

  id: String

  body: String

  title: String

}

type Query {

  foods: [Food]

}

The Food describes the look of a single food object on our server. The Query type states the shape the queries will have. The foods query shape states that the query will return an array of Food objects.

GraphQL is ideal for developing real-time apps. We will see it below.

Subscriptions in GraphQL

Subscriptions are what GraphQL employs to deliver real-time updates from the GraphQL server to the subscribed clients.

It is just like using Socket.io to set up real-time communication from the server to the frontend. Here, GraphQL has it inbuilt into its functionality.

GraphQL subscriptions are made majorly to listen to when data is created on the server, when a data is updated, when a data is deleted, and when a data is read via query. The event to emitted is dependent on what the dev wants. The events are pushed from the server to the subscribing clients.

Clients subscribe to a server-side event by using the subscription query:


subscription NewsFeed {

  newsCreated {

    title

    body

  }

}

This subscription will be for the newsCreated field.

This will open up a long-lived connection to the server by sending the subscription query specifying which event it is interested in.

The above subscription query sends a query via WebSocket to the GraphQL server, the query sets up an event in the server with a resolver callback function. Whenever the event is emitted in the GraphQL server, the resolver function is called, the return value from the called is sent down to the subscription query.

Subscriptions use WebSocket for communication, not the normal POST method queries and mutations use. Subscriptions maintain an active connection to the server listening and waiting for the server to push updates to it.

Setting up subscriptions

Subscriptions are quite easy to set in GraphQL.

Now, let's say we want to add a subscription to be notified when a new news item is added to our database.

We will start by defining the Subscription type in the schema.


type Subscription {

  newNewsItem(id: String): NewsItem

}

This is our newNewsItem GraphQL schema definition.

Next, we will create a resolver for the newNewsItem field. Now, resolvers for Subscription are different from the ones for queries and mutations.

Subscription resolver is an object that will have the resolve and subscribe function properties. The subscribe must return an object of type AsyncIterator.


 Subscription: {

    newOrder: {

      resolve: (payload) => {

        return payload.newNewsItem;

      },

      subscribe: () => {

        return pubsub.asyncIterator("NEW_NEWSITEM");

      },

    },

  },

The subscribe function is called when the Subscription query is made from the client-side. The pubsub is an object of PubSub. PubSub is from the apollo-server-express library.

The PubSub enables us to publish events to a label and also listen for events associated with a label. In general, PubSub is a basic event emitter that lets us emit and listen to events.

The code pubsub.asyncIterator("NEW_NEWSITEM"); subscribes NEW_NEWSITEM label to PubSub in-memory event bus.

Let's into the resolve function.

The payload parameter holds the payload that is emitted alongside the NEW_NEWSITEM event. The newNewsItem is referenced from the payload and returned, this is sent to the active connection waiting on the client-side.

Emitting the event label is done using the publish(...) method in PubSub.


pubsub.publish("NEW_NEWSITEM", {

  newNewsItem: newsItem,

});

This code will make the resolve function in our subscription resolvers be called. The second argument above will be received by the resolve function in its payload parameter.

The NEW_NEWSITEM is emitted from the mutation that adds a new news item to the database. Let's see the code:


addNewsItem(parent, args, context, info) {

      const { body, title } = args;

      const newsItem = new NewsItem({

        body,

        title

      });

      return newsItem

        .save()

        .then((result) => {

          const newNewsItem = { ...result._doc };

          pubsub.publish("NEW_NEWSITEM", { newNewsItem  });

          return newNewsItem;

        })

        .catch((err) => {

          console.error(err);

        });

    },

This addNewsItem is a mutation, it adds a new news item to the database. See that after the successful addition of a new news item to the database we emit the NEW_NEWSITEM event using PubSub#publish method pubsub.publish("NEW_NEWSITEM", ...). This pushes the new news item to the client that is subscribed to the NEW_NEWSITEM.

We need to tell the ApolloServer about the subscriptions. To do that we call the method ApolloServer#installSubscriptionHandlers passing in the Express.js server instance as an argument.


server.installSubscriptionHandlers(httpServer);

When we start the server, we can open a tab in our browser and navigate to http://localhost:3000/graphql, then create another tab and also navigate to http://localhost:3000/graphql.

On the first tab, run the subscription query in the playground UI:


subscription($title: String, $body: String) {

  newNewsItem(title: $title, body: $body) {

    title

    body

  }

}

You will see that a spinner will appear. This means that it is listening for events from the server.

Now, on the second go there and run the mutation:


mutation {

  addNewsItem(body: "Some news", title: "news title") {

    body

    title

  }

}

Move back to the subscription tab, we will see that the following contents:


{

    data: {

        newNewsItem {

            body: "Some news",

            title: "news title"

        }

    }

}

is displayed. This is the new news item that was recently added in the mutation tab, so the mutation was done and the event NEW_NEWSITEM was emitted, this enabled the subscription tab to capture the payload and display it.

Notice that the subscription is still listening to the event till the block button is clicked to cancel it.

This is how GraphQL subscriptions work.

We see now that this opens up a whole lot of possibilities open for us in GraphQL. We can build real-time apps using the subscriptions, no need for extra infrastructure or library because everything is built into the core of GraphQL.

News App

For a full demo, let's build a blog app server. We will perform the mutation to add blog posts from the GraphQL playground.

The server will be an Express.js server so we scaffold a Nodejs project.


mkdir grp-sub

The above command creates a folder named grp-sub, let's move into the folder.


cd grp-sub

Init Node.js environ:


npm init -y

Let's create the files index.js, resolvers.js, schema.js.


touch index.js resolvers.js schema.js

Now, we have to install dependencies:

express: Fast, unopinionated, minimalist web framework for node.

graphql: The JavaScript reference implementation for GraphQL, a query language for APIs created by Facebook.

apollo-server-express: This is the Express and Connect integration of GraphQL Server.

express-graphql: Create a GraphQL HTTP server with any HTTP web framework that supports connect styled middleware, including Connect itself, Express and Restify.


npm i express graphql apollo-server-express express-graphql

Open index.js and add the below code:


const express = require("express");

const { ApolloServer } = require("apollo-server-express");

const { createServer } = require("http");



const app = express();

const server = new ApolloServer({

  playground: {

    endpoint: "http://localhost:3000/graphql",

    settings: {

      "editor.theme": "light",

    },

  },

});

server.applyMiddleware({ app });



const httpServer = createServer(app);



httpServer.listen(3000, () => {

  console.log("connected!");

});

We required the libraries and created an Expressjs instance in app.

Next, we created an instance of ApolloServer, we passed in an object in its constructor. The object holds the playground config. It sets the endpoint of the playground to be localhost:3000/graphql and the editor theme to be light theme.

Now, we write our schemas.


const { gql } = require("apollo-server-express");



const typeDefs = gql`

  type BlogPost {

    _id: String

    title: String

    body: String

    postImage: String

  }



  type Aggregate {

    count: String

  }



  type BlogPosts {

    nodes: [BlogPost]

    aggregate: Aggregate

  }



  type Query {

    blogPosts: BlogPosts

    blogPost(id: String): BlogPost

  }



  type Mutation {

    addBlogPost(title: String, body: String, postImage: String): BlogPost

  }

`;



module.exports = typeDefs;

This defines the type definitions of our queries and mutations and their results. It describes how their shape will look like.

We will have to add the type definitions for our subscriptions.


const { gql } = require("apollo-server-express");



const typeDefs = gql`

  ...

  type Mutation {

    addBlogPost(title: String, body: String, postImage: String): BlogPost

  }



  type Subscription {

      newBlogPost: BlogPost

  }

`;



module.exports = typeDefs;

We have our type definition for subscriptions in our app. The newBlogPost subscription must have a return type of BlogPost.

Now, let's move to our resolvers file and add the below code:


const { PubSub } = require("apollo-server-express");

const _r = require("./data.json");



const pubsub = new PubSub();



const resolvers = {

  Query: {

    blogPosts(parent, args, context, info) {

      const blgPosts = _r.blogPosts;

      return {

        nodes: blgPosts,

        aggregate: {

          count: blgPosts.length,

        },

      };

    },

    blogPost(parent, args, context, info) {

      const blgPosts = _r.blogPosts;

      const id = args.id;

      return blgPosts.find((r) => r.id == id);

    },

  },

  Mutation: {

    addBlogPost(parent, args, context, info) {

      const { title, body, postImage } = args;

      const newBlogPost = {

        id: Date.now(),

        title,

        body,

        postImage,

      };

      _r.blogPosts.push(newBlogPost);

      pubsub.publish("NEW_BLOGPOST", { newBlogPost });

    },

  },

  Subscription: {

    newBlogPost: {

      resolve: (payload) => payload.newBlogPost,

      subscribe: () => pubsub.asyncIterator("NEW_BLOGPOST"),

    },

  },

};



module.exports = resolvers;

The _r contains our mock blog post data.

It is required from the data.json file.

We required PubSub from apollo-server-express and created an instance of it and stored it in pubsub.

Next, we created the resolvers object, the Query object holds the resolvers for all the queries in our app. See it has queries to read all blog posts blogPosts and to read a blog post blogPost.

Mutation object holds the mutation functions. Here, we have only one mutation function addBlogPost. It adds a new blog post to our database.

Subscription object holds our subscriptions. Here, we have newBlogPost subscription. Its resolve function property returns the emitted payload. The subscribe function subscribes to the "NEW_BLOGPOST" event. The resolve function will be called when the event "NEW_BLOGPOST" is emitted, receiving the payload sent along in its payload parameter.

In the addBlogPost mutation function, see that we emitted the "NEW_BLOGPOST" event when the new blog post is pushed to the blog posts array. This is done using PubSub#publish method. pubsub.publish("NEW_BLOGPOST", { newBlogPost });, the first arg is the event name "NEW_BLOGPOST", and the last arg is the new blog post object passed in an object.

We have one more thing to do before we start our server.

Go to index.js. We need to import our resolvers and schema objects here and pass them to ApolloServer constructor.


...

const typeDefs = require("./schema");

const resolvers = require("./resolvers");



const app = express();

const server = new ApolloServer({

  typeDefs,

  resolvers,

  playground: {

    endpoint: "http://localhost:3000/graphql",

    settings: {

      "editor.theme": "light",

    },

  },

});

...

Now, we need to activate the GraphQL subscription handlers in ApolloServer.:


...

server.installSubscriptionHandlers(httpServer);

...

Now, start the server:


node .

connected!

Open your favorite browser and navigate to http://localhost:3000/graphql, you will see the playground UI appear, so run the subscription.


subscription {

  newBlogPost {

    title

    body

  }

}

The playground will start listening to event updates from the GraphQL server.

Create a second tab and load http://localhost:3000/graphql on it. Now, we perform the addBlogPost mutation.


mutation {

  addBlogPost(

    title: "Intro Svelte"

    body: "Svelte content"

    postImage: "no image"

  ) {

    body

    title

  }

}

This will add a new blog post about "Svelte" to the database. Click on the "Play" button. This will run the mutation, the data section will show this:


{

  "data": {

    "addBlogPost": {

      "body": "Svelte content",

      "title": "Intro Svelte"

    }

  }

}

The new blog post.

Now, move to the subscription tab, we will see that the new blog post is displayed on the data section.


{

  "data": {

    "newBlogPost": {

      "title": "Intro Svelte",

      "body": "Svelte content"

    }

  }

}

FAQs

Let's address some frequently asked questions.

What are the benefits of GraphQL subscriptions?

GraphQL subscriptions are very useful in creating real-time apps in GraphQL. With what we have learned here, we can create a chat app with ease.

We can also create a web analytics app using GraphQL. The real-time communication is built-in in the GraphQL core so it becomes easy for us to use it.

Most companies using GraphQL in their API use the subscription to serve content in real-time.

What transport does Apollo use to implement subscriptions?

Apollo uses WebSocket to implement subscriptions. Unlike the POST method used in the conventional GraphQL query.

A GraphQL subscription query performs the query over the WebSocket endpoint. The subscription listens for data pushed from the server via the WebSocket.

Conclusion

We covered a lot about GraphQL subscriptions in this article.

First, we started by clearly describing GraphQL, how it works and the goodies it brings over to modern software development.

Next, we described GraphQL subscription, with vital points on how it works and at the same time explaining with examples. Further down, we developed a real Expressjs-backed GraphQL server using Apollo to demo how subscriptions work.

Subscription is one of the powerful features of GraphQL.

Why not level up your reading with

Stay up-to-date with the latest developer news every time you open a new tab.

Read more