Creating GraphQL Subscriptions in Express ― Scotch

Introduction

This section focuses on creating realtime applications using GraphQL, and the best way to achieve that is through subscriptions. The code in this section is a continuation of Part 1 of this series, and I would advise going through it before moving forward. This is a critical and essential part to future implementation of GraphQL subscriptions on a React client using Apollo.

GraphQL subscriptions are a way to push data from the server to the clients that choose to listen to real time messages from the server. Subscriptions are similar to queries in that they specify a set of fields to be delivered to the client, but instead of immediately returning a single response, a result is sent every time data is published to a topic.

A common use case for subscriptions is notifying the client side about particular events, for example the creation of a new object, updated fields and so on.

Setup

We will continue with the same application structure from Part 1.
The process of setting up a GraphQL server with subscriptions involves:

  • Declaring subscriptions in the schema.
  • Setup a PubSub instance that our server will publish new events to.
  • Hook together the PubSub event and the GraphQL subscription.
  • Setting up SubscriptionServer, a transport between the server and the clients.

We will use the PubSub implementation from graphql-subscriptions, and we will connect it to subscribe executor of GraphQL, and publish the data using the subscriptions-transport-ws (a WebSocket server and client library for GraphQL that can be used directly in a JavaScript app or wired up to a fully-featured GraphQL client like Apollo or Relay). The diagram below shows a general overview of the flow of information using subscriptions.

Declaring Subscriptions in the Schema

Adding subscriptions to the schema is similar to adding queries or mutations by specifying the operation type and the operation name. In the following snippet, we declare a subscription and an operation called channelAdded.

// src/schema.js
...
# The subscription root type, specifying what we can subscribe to
type Subscription {
    channelAdded: Channel    # subscription operation.
}
...

Subscription Resolver

We create a resolver just like queries and mutations, but instead of function, we pass an object with a subscribe field and a subscription resolver method. The subscription resolver method must return AsyncIterator, which is provided by the asyncIterator method of your PubSub.

// src/resolvers.js
import { PubSub } from 'graphql-subscriptions';
...
const pubsub = new PubSub(); //create a PubSub instance
const CHANNEL_ADDED_TOPIC = 'newChannel';
export const resolvers = {
...
Mutation: {
    addChannel: (root, args) => {  //Create a mutation to add a new channel.
      const newChannel = { id: String(nextId++), messages: [], name: args.name };
      channels.push(newChannel);
      pubsub.publish(CHANNEL_ADDED_TOPIC, { channelAdded: newChannel });  // publish to a topic
      return newChannel;
    }
  },
  Subscription: {
    channelAdded: {  // create a channelAdded subscription resolver function.
      subscribe: () => pubsub.asyncIterator(CHANNEL_ADDED_TOPIC)  // subscribe to changes in a topic
    }
  }
}

What does the above snippet do?

We construct an instance of PubSub to handle the subscription topics for our application using PubSub from graphql-subscriptions.
In our mutation where we add a new channel, we also publish it to the newChannel topic; it will then be received by clients that are subscribed to this topic.

Finally, we declare a subscription with an operation channelAdded which will map to the same in our schema and returns an AsyncIterator that will emit the messages to send over to the client.

Setting Up a Subscriptions Server

The last step is to add subscription support to our GraphQL server through WebSockets using subscriptions-transport-ws package, since we can’t push frequent updates from the server to the client over HTTP. We begin by importing the necessary packages.

// server.js
...
import { execute, subscribe } from 'graphql';
import { createServer } from 'http';
import { SubscriptionServer } from 'subscriptions-transport-ws';
...

We then open the WebSocket in our GraphQL server first by wrapping the Express server with createServer, and then using it to set up a WebSocket to listen to GraphQL subscriptions.

// server.js
// Create an express server.
const server = express();
...
// Wrap the express server.
const ws = createServer(server);
ws.listen(PORT, () => {
  console.log(`GraphQL Server is now running on http://localhost:${PORT}`);

  // Set up the WebSocket for handling GraphQL subscriptions.
  new SubscriptionServer({
    execute,
    subscribe,
    schema
  }, {
    server: ws,
    path: '/subscriptions',
  });
});

We then configure GraphiQL to use the subscriptions web socket we setup.

// server.js
...
server.use('/graphiql', graphiqlExpress({
  endpointURL: '/graphql',
  subscriptionsEndpoint: `ws://localhost:${PORT}/subscriptions` // subscriptions endpoint.
}));
...

Testing

We then navigate to http://localhost:7900 in our browser and test out the subscription. We should be presented with the message:

“Your subscription data will appear here after server publication!”

We then open another browser window and create a mutation that creates a new channel. When the mutation operation is successful, we should see the results immediately in the subscription window.

Conclusion

With advancing technology, most users are more comfortable receiving immediate results rather than waiting for reloading pages and this is achieved by the implementation of realtime systems. GraphQL ensures realtime transfer of minimal amount of data. This allows for development of applications that are extremely fast in meeting user needs.

Leave a comment

Your email address will not be published.