GraphQL concepts I wish someone explained to me a year ago

Part 6: Subscriptions (server implementation)

Naresh Bhatia
Naresh Bhatia
6 min readDec 26, 2018

--

Image by Rostyslav

In part 5 of this series we implemented mutations on the Bookstore client. In this part, we jump back to the server side and implement subscriptions.

One part will be released every day this week. Follow me or subscribe to my newsletter to get updates on this series.

Source Code

You can download the code for this part by executing the following commands. Skip the first command if you have already cloned the repository.

Test Drive

Before diving into the implementation, let’s look at how the final implementation works. Make sure you have yarn installed on your machine. Now execute the following commands:

This will start the server. Point your browser to http://localhost:4000/ — it will show the GraphQL Playground. You’ve already used the Playground in part 2, so it needs no introduction.

Remember, subscriptions are a way to display changes in real time. Our discussion from part 1 states:

GraphQL subscriptions are a way to push data from the server to the clients in real-time. As defined by the GraphQL specification: “subscription is a long‐lived request that fetches data in response to source events”. In fact, subscriptions are very similar to queries in that they specify a set of fields to be returned. However, instead of returning them immediately, the server sends them to the requesting client every time a specified event happens.

Let’s start by looking at a subscription operation declared in the updated schema. Here’s the authorMutated subscription:

Here’s how to interpret this declaration:

If a client subscribes to the authorMutated event, it will be sent the AuthorMutationPayload whenever that event happens. AuthorMutationPayload is an object with two fields:

  • mutation: indicates what type of mutation was performed: CREATED, UPDATED or DELETED
  • node: The author that was created, updated or deleted

With this background, let’s subscribe to the authorMutated event from the Playground. In the left panel, enter the following subscription and click the Execute button.

The right panel will now show “Listening …”.

Open a new tab in Playground and create an author by entering this mutation

along with the following query variable:

When the author is successfully created, switch back to the subscription tab. You will see the following subscription response. Note that the mutation type is CREATED.

Now open another tab in Playground and update the name of the author by entering the following mutation:

along with the following query variables (use the author id that you received):

When the author is successfully updated, switch back to the subscription tab. You will see the following subscription response. Note that the mutation type is UPDATED.

Subscription Implementation

Declaring the schema

The first step to implement a subscription is to declare it in our schema. We’ve already seen the declaration for the authorMutated subscription. Look over the declarations of publisherMutated and bookMutated — they are similar.

Creating up a PubSub instance

The next step is to create a PubSub instance which is responsible for managing subscriptions on the server. PubSub is a class that exposes a publish and a subscribe API. Here’s the code that creates a global PubSub instance to be used in our server:

Creating subscription resolvers

Just like other schema fields, we need to create resolvers for our subscription operations. However, instead of a function, the subscription resolvers requires an object with a subscribe method that return an AsyncIterator. We obtain the AsyncIterator using our global pubsub object. This iterator is responsible for emitting published messages to all the subscribed clients.

Putting this together, here’s the authorMutated resolver.

The other subscription resolvers are similar. Take a look to see if they make sense.

Publishing subscription events

Now that we’ve provided a mechanism for clients to subscribe to events, the only thing left is to publish those events. For example, we need to publish the AUTHOR_MUTATED event whenever an author is created or updated. Let’s enhance the mutation resolvers to do exactly that.

Here we call pubSub.publish() to publish theAUTHOR_MUTATED event along with the desired payload.

Scaling subscriptions

By default, the Apollo Server uses a PubSub implementation provided by graphql-subscriptions. This implementation does not scale well as described in the docs:

Note that the default PubSub implementation is intended for demo purposes. It only works if you have a single instance of your server and doesn’t scale beyond a couple of connections. For production usage you’ll want to use one of the PubSub implementations backed by an external store. (e.g. Redis)

To scale subscriptions beyond a single GraphQL server, we need a more robust pub/sub mechanism that can distribute published messages over multiple GraphQL servers. Here’s the logical architecture for such a system.

Scaling subscriptions

Note that in this architecture there are event sources and listeners other than the GraphQL servers themselves, e.g. the two micro-services shown on the left. In fact, if we upgrade to this architecture for our bookstore, I would recommend having a Bookstore micro-service. This micro-service would connect to our persistence data store and be responsible for all the CRUD operations. Moreover, publishing mutation events (which is currently happening inside the GraphQL resolvers) would move to the Bookstore micro-service, which is now responsible for mutations. In this scenario, the GraphQL servers will only subscribe to mutation events.

Let’s use the publish/subscribe capabilities of Redis to implement just the event bus for now (not the full architecture). We will switch to the graphql-redis-subscriptions library to do this. Follow the steps below:

  1. Install and start a Redis server. Here’s an article that describes how to install Redis on MacOS.
  2. Open graphql/pubsub.ts. Comment out the default PubSub implementation (lines 2–3) and uncomment the Redis implementation (lines 7–8).
  3. Restart the Bookstore server.

Now subscriptions will use the Redis server for publish/subscribe. This will scale significantly better.

Summary

We’ve now covered the subscription implementation on the server. This is what we learned:

  • How to declare subscriptions in our schema
  • How to create a PubSub instance to support subscriptions
  • How to write a subscription resolver
  • How to publish subscription events
  • How to scale subscriptions using a Redis implementation of PubSub

With only one part left in the series, I’d love to get your questions and comments.

In part 7, we will implement subscriptions in our React client.

Read Part 7: Subscriptions (client implementation) >

Resources

  • GraphQL Subscriptions by Robert Zhu: In this presentation, Robert starts from the official (abstract) definition of subscriptions and explains what it means in practical terms.
  • GraphQL Subscriptions by Urigo: Uri demystifies the magic behind GraphQL subscriptions and shows how anyone can implement them.

--

--