Apollo GraphQL subscriptions

Sergey Dolin
Aug 9, 2017 · 6 min read

There are at least 3 attempts to describe apollo graphql subscriptions in the Internet. And none of them worked for me. Somehow we, in S4Y, got a success a year ago, then just copy-pasted the working code till we faced new releases of countless dependencies and the error messages even Stackoveflow can not help to get rid of.

While the most parts of Apollo code and ideas are really brilliant but subscriptions related modules and their docs are terrible. They definitely assume the reader is passionate about queues, pubsub models and are aware of some exotic patterns.

The others internet articles are “chatting” samples which buried the sense among details like the implementation GraphQLDate custom datatype or scaphold.io authorizations. Honestly i could not afford to dig through few screens of chaotic fragments of code intermixed with dots and bolds. Another article intermix terms “topic” and “channels” with no least mentioning what they are. And the other author thought it would be nice to type “addComment” in tree different positions made me to guess should it be a constant to link those positions together or he was just lazy to type different names.

But, enough to woe. Let’s start to share the success.

First we need to create a WebSocket server and connect GraphQL subscriptions server. Roughly it’ something like:

const { createServer } = require("http");
const { SubscriptionServer } = require("subscriptions-transport-ws");
const wsServer = createServer ...
wsServer.listen(config.port,...
const subscriptionsServer = new SubscriptionServer({...,server: wsServer})

SubscriptionServer constructor parameters are weird and chaotic. Nobody knows why did they decided to have 2 objects but that is not the worst. The worst is the first parameter must include 2 “magic” attributes nobody knows once again what they are and what they are for.

const { execute, subscribe } = require('graphql');const subscriptionsServer = new SubscriptionServer({
execute,
subscribe,
...

Actually i am really worry about the future of Apollo project while seeing these lines.

The next attribute is “schema”

const subscriptionsServer = new SubscriptionServer({
execute,
subscribe,
schema,
...

It should be explained before all what’s the “schema” is, but “chicken and egg” is common problem with GraphQL subscription, so it’s not very important to choose the right start. “Schema” is well known graphql schema with one note: it is not “Long string definition of the GraphQL types”, it is “compiled” apollo-specific(?) representation of the schema created with the special function from “graphql-tools” module:

const { makeExecutableSchema } = require("graphql-tools");
const resolvers = require("./apollo-gql-resolvers");
const typeDefs = `
...

type Subscription {
...
heartbeat:String!
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
`;
const schema = makeExecutableSchema({ typeDefs, resolvers });

The official doc limits the first constructor parameter to an object of these 3 attributes: execute, subscribe, schema. But in real life i believe everybody needs the fourth:

const subscriptionsServer = new SubscriptionServer({
execute,
subscribe,
schema,
onSubscribe: (message, params, webSocket) => {...}

onSubscribe event is the essential place to set up the resolvers context through the “params” parameter. The “message” is “input from the client(browser)” and “params” is “output to graphql resolvers context”.

Now, we have SubscriptionServer semi-magically created and connected to WebSocket server/transport (they use terms “server” and “transport” as if they can not decide which is better and roll a dime every time they have to chose).

But now a time for the real black magic. To send a payload from the code to the subscription server they introduced a very strange creature sometimes called “pubsub” and sometimes “subscription”.

It is created as easy as

const { PubSub } = require("graphql-subscriptions");
module.exports = new PubSub();

That’s all. They could not live without this 2 lines file. You won’t as well. The singleton is the work horse and they could not invent another way to create it .

This pubsub (i prefer to stay with this short word) instance will be used to send the payload to the server and further to the client(browser):

const pubsub = require("./lib/apollo-gql-subscription-pubsub");
setInterval(()=>{
pubsub.publish('HEARTBEAT',{heartbeat: new Date()})},
3000);

This snippet may be placed anyway and seems to be not connected to the Subscription Server instance and/or graphQL engine in any way. Let’s try to trace the connection…

You may notice i already defined the subscription in the schema:

const typeDefs = `
...

type Subscription {
...
heartbeat:String! <<<<===============
}
schema {
query: Query
mutation: Mutation
subscription: Subscription
}
`;

and thus we have to have something in the resolvers

const resolvers = {
Query: ...
Mutation: ...
Subscription: {
heartbeat: {
...
}
}
}

Yes! Subscription resolver is not a function with root, args, ctx parameters. It is an object. An object with strange attribute:

const pubsub = require("./apollo-gql-subscription-pubsub");const resolvers = {
Query: ...
Mutation: ...
Subscription: {
heartbeat: {
subscribe: () => pubsub.asyncIterator("HEARTBEAT")
}
}
}

Don’t ask me what is “asyncIterator” and “HEARTBEAT”. I really have no idea about asyncIterator and “HEARTBEAT” is that they sometimes call “topic” and sometimes “channel”. I intentionally used caps to distinguish it from “heartbeat” as a name of the … well… let’s cal it “subscription” meaning an identificator used in the schema and mirrored in the resolvers.

It looks like having “pubsub” on the left of asyncIterator binds somehow pubsub with GraphQL engine. Let’s believe in it and let dwarves to search the connection between pubsub and Subscription Server.

Finally we have almost everything ready on the server side. The only thing to know about is pubsub.publish() function.

It has has 2 parameters. The first one is the string an it must be the same as the string used within asyncIterator, i.e. HEARTBEAT in our case. The second parameter is an object called in docs “payload” which MUST have the only attribute with the very same name as a subscription name, i.e. “heartbeat” in our case.

pubsub.publish('HEARTBEAT',{heartbeat: new Date()})

Now you are all set. Redux Dev Tools in the chrome should show you the incoming actions if the client set up right.

Let’s see the client.

I hesitate should i tell about the network client now. They are having itch and burning in their asses to break everything and change it “to better” in next release. Nevertheless:

import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws';// create websocket connectionconst wsClient = new SubscriptionClient("http://...", {reconnect: true})// "add" it to the "ordinal" Graphql network interface const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
networkInterface, // it was created somewhere before
wsClient
);

It is essential but useless configuration that should let you get notifications from the server sent through pubsub.publish call.

It’s useless because of i could hardly imagine the real world application without authentication and we need to pass the credentials from the browser to the server somehow. They suggest to pass the credentials only on the connect and it would work for authentication purposes only. I used to pass GWT token with every request and use it to get the user info without querying the database. My solution come from the ordinal graphql interface but i use the same with the subscriptions:

wsClient.use([{
applyMiddleware: (options, next) => {
options.jwt = store.getState().auth.token;
next();
}
}]);

This middleware adds jwt to something the called “options” which turns to be “message” on the server. Remember onSubscribe i mentioned above:


onSubscribe: (message, params, webSocket) => {
if (!message.jwt) <<<< ==== here it is!
return params;
return new Promise( // jwt.verify is async
(resolve, reject) =>
jwt.verify(message.jwt, SECRET,
(err, decoded) =>{
if (err)
reject(err);
params.context.auth=decoded; <<== pass user data
resolve(params);
})
);

and now we have auth whenever an context is referenced.

Ughh… Now it seems i failed like others to author useful text about Apollo graphql subscription. I wonder how did they manage made such simple thing such complex. Especially comparing with the rest of Apollo which is much more complex by the nature but so clear and easy to use.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade