GraphQL Subscriptions Using Apollo 2

Written by jlowery2663 | Published 2018/12/29
Tech Story Tags: graphql | subscriptions | apollo | websockets | latest-tech-stories | graphql-apollo | apollo-2 | graphql-api

TLDR A side project involves writing a GraphQL API around the Universal Chess Interface (UCI) I originally developed a mock implementation and wrote about it awhile back. This post will cover the most basic implementation of subscription service and the client code that uses it. The GraphQL server will be based on ApolloServer, which will use an Express server as middleware. Subscriptions will use a WebSocket that is attached to a plain HTTP server (also managed by ApolloServer) There are two clients: the GraphQL Playground, and a client for use by Mocha to test the API.via the TL;DR App

One of my side projects involves writing a GraphQL API around the Universal Chess Interface (UCI). I originally developed a mock implementation and wrote about it awhile back. That used version one of Apollo’s client/server stack, which changed quite a bit in version two. One of the biggest changes was the subscriptions interface and implementation, and I struggled for many hours to get that to work. Part of the problem is that the documentation is not very complete and rather scattered. This post will cover the most basic implementation of subscription service and the client code that uses it.

Getting started

The GraphQL server will be based on ApolloServer, which in this case will use an Express server as middleware. Subscriptions will use a WebSocket that is attached to a plain HTTP server (also managed by ApolloServer, as we shall see).
There are two clients: the GraphQL Playground, and a client for use by Mocha to test the API outside of a GUI. For simplicity, I’ll keep the Mocha client code in the same folder as the server code. I also use Babel so I don’t have to think about Michael Jackson Scripts and the like.
With that in mind, here are the packages to be installed:
  • apollo-link, apollo-link-http, apollo-link-ws, apollo-server-express
  • babel-cli, babel-core, babel-preset-env
  • graphql, graphql-tools
  • subscriptions-transport-ws
  • mocha, chai
I’ll provide a link to a working code repository at the end of this article.

The Server

The server is as basic as I could make it:
import express from 'express';

import { ApolloServer } from 'apollo-server-express';
import http from 'http';
import { typeDefs } from './schema';
import resolvers from './resolvers';

const PORT = 3031;
const app = express();


const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.applyMiddleware({
  app
});

const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);

httpServer.listen({ port: PORT }, () => {
  console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`);
  console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`);
});
An ApolloServer is created with schema and resolver parameters. The applyMiddleware call passes in an express application instance. Finally, an HTTP server is created to handle the web socket interface to the subscription service. Both GraphQL and the subscription websocket share the same port, but under different protocols.

The Schema

import { makeExecutableSchema } from 'graphql-tools';
import resolvers from './resolvers';

const typeDefs = [
  `
  type Query {
    go: String!
  }
  type Subscription {
    info: String!
  }
`,
];

const options = {
  typeDefs,
  resolvers,
};

const executableSchema = makeExecutableSchema(options);
export default executableSchema;
export { typeDefs };
The schema is very basic: go will return a string acknowledgment, but behind the scenes it will fire off a method that sends publish notifications to subscribers. Subscribers subscribe through the info subscription API.
We can see how this works in resolvers.js:
import { PubSub } from 'apollo-server-express';
const pubsub = new PubSub();

const TOPIC = 'infoTopic';

const infos = ['info1', 'info2', 'info3', 'done']

const publish = () => {
    setTimeout( () =>
    infos.forEach(info => pubsub.publish(TOPIC, {info})), 1000)
}

export default {
    Query: {
      go: () => { publish(); return 'going'}
    },
  
    Subscription: {
      info: {
        subscribe: () => pubsub.asyncIterator([TOPIC]),
      },
    },
  };
I’ve made the resolvers deliberately simple. For example, the asyncIterator method from PubSub is quite sophisticated (it supports filters and authentication, for example). Since it is already well-documented, I won’t delve into various options here. All the resolver above does is to publish an infoTopic to all subscribers, passing info objects as the body of the subscription.

The Client

As I mentioned, I’ll use two clients for testing. One is the built-in Prisma Playground, a service automatically provided by the ApolloServer (FYI: you can turn this feature off in production).

Testing in Playground

To test, I’ll open Playground in Chrome, at http://localhost:3031/graphql. Within Playground you can open up multiple tabs. For the first, I’ll subscribe to the infoTopic:
When this command is played, you will see a ‘Listening …’ response in the right pane, as shown above. Now you’re subscribed.
Open another tab within Playground and query go:
This sends back a response of “going”, so everything is okay. Now, going back to the subscription tab, you will see the published output:

Testing using Mocha/Chai

There’s a lot of boilerplate in setting up a client, so I’ve created a utility file to handle query and subscribe operations. It’s called fetch.js:
/* eslint-env mocha */
/* eslint-disable import/no-extraneous-dependencies */
// see https://www.apollographql.com/docs/link/index.html#standalone
import fetch from 'node-fetch';

import { execute, makePromise } from 'apollo-link';
import { WebSocketLink } from 'apollo-link-ws';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import ws from 'ws';
import { HttpLink } from 'apollo-link-http';
import gql from 'graphql-tag';

const uri = `http://localhost:3031/graphql`;
const wsUri = `ws://localhost:3031/graphql`;

const link = new HttpLink({ uri, fetch });
const wsClient = new SubscriptionClient(wsUri, { reconnect: true }, ws);
const wsLink = new WebSocketLink(wsClient);

const doQuery = (query) => {
  const operation = {
    query: gql`${query}`,
  };

  console.log(`$query is ${query}`)
  return makePromise(execute(link, operation));
};

const subscribe = (query, handlers) => {
  const operation = {
    query: gql`${query}`,
  };

  return execute(wsLink, operation).subscribe(handlers);
};

export default doQuery;
export { subscribe };
The fetch.js code pulls in a lot of interdependent modules. For the test operations, I use apollo-link to handle the interface between the server (which has to be running, btw 😉) and the GraphQL operations used by the tests. There are actually two interfaces here: one to handle query and mutation requests vis HTTP, and another that goes through a web socket to subscribe to a topic.
Most of this comes from the documentation, but you will have to install a few additional modules I neglected to mention earlier:
  • node-fetch (installed globally via npm -g)
  • ws
  • gql
The node-fetch module implements the HTTP Fetch API that’s becoming a standard. The ws module is a WebSocket implementation for Node (there are others), and gql is a template literal tag that you can read more about here.
The test itself is pretty straightforward: we first subscribe, then go:
/* eslint-env mocha */
import chai from 'chai';
import fetch, { subscribe } from './fetch';

chai.should();

describe('subscribe, then go', function () {
  this.timeout(5000);

  const handlers = {
    next: (data) => {
      console.log(`received data:`, data);
      if (data.data.info === 'done') {
        console.log('exiting...')
        process.exit(0);
      }
    },
    error: error => console.error(`received error ${error}`),
  };

  // get the subscriber ready...
  it('subscribe', async () => {
    const query = `subscription {
          info
        }`;
    subscribe(query, handlers);
  });

  it('go', async (done) => {
    const query = `query {
            go
        }`;

    try {
      const res = (await fetch(query)).data;
      res.should.have.property('go')
      res.go.should.equal('going')
    } catch (e) {
      console.error(e)
      done(e)
    }
  })

})
The hander object (passed to the subscribe method) is what receives the publish notifications. When the last published message is received, the test process is terminated.

Conclusion

You should explore the links provided, as there is a lot more about GraphQL subscriptions that I wasn’t able to cover in this article. I’ve also included a few relevant links, below. A complete github repository containing the code above can be found here. Enjoy!

Written by jlowery2663 | I'm an early adopter kinda guy.
Published by HackerNoon on 2018/12/29