Pros and Cons of GraphQL Subscriptions
By: Robin Huang
Tags: Graphql Subscriptions, GraphQL, Websocket, Apollo Server, Apollo Client

Recently my team at Puffin made a migration over to GraphQL. If you love Typescript, you will love GraphQL. Having a declarative schema that acts as a contract between the front-end and backend is life-changing. This was mind-blowing for me.

Gone are the days of manually typing API response on the front-end, which can break with any backend deploy. Now we use graphql-tool to generate all our API calls. The entire ecosystem and documentation around GraphQL is pretty awesome actually.

This took care of API calls. However, the application we develop also has a real-time component. We need to push updates to the client as soon as they happen. Since we make a web-app using React, we do this through websockets, like most projects.

While reading about GraphQL through Apollo’s documentation, I realized they have something called subscriptions as well. What are subscriptions?

Subscriptions are a type of GraphQL query that do not end. It’s like an Observable stream; it will keep producing data. They are commonly implemented via Websockets. An example is a subscription that tells you about new comments added to a Facebook post. Or new messages sent to a chat.

I was really intrigued and decided to try it out. When you buy into the Apollo ecosystem, you gain a lot. For example, subscriptions work nicely with the Apollo inMemory Cache. You can execute a query, and then attach a subscriber to that query so that changes are merged in with the data from the original query. Would it be possible to replace plain Websockets or socket.io with GraphQL subscriptions?

While GraphQL Subscriptions work great for some use-cases, I discovered some major drawbacks while experimenting. If you’re not familiar with them, take a look at the Apollo documentation.

It’s very hard to hook into the lifecycle of an individual subscription. The Apollo Server gives you onConnect and onDisconnect for the websocket connection, but not for an individual subscription.

Why is this? The name suggests the answer. Subscriptions are purely a way for the client to listen to changes the server publishes. The server does not care about the client’s state. This is limiting for many reasons.

Let’s say you want to subscribe to a Post, but your TCP connection breaks for 5 seconds. In the meantime, you lose a few updates the server sent. On reconnection, it would be great if you could send the lastTimestamp you have for the chat via websocket and have the server send the updates. But you can’t, because you can’t attach any information to the start of any subscription.

Let’s look at the code for Apollo Subscriptions:

const resolvers = {

Query: () => { ... },

Mutation: () => { ... },

Subscription: {

commentAdded: {

subscribe:

withFilter(

# subscription resolver

() => pubsub.asyncIterator('COMMENT_ADDED'),

# Filter logic (payload, variables) => { return payload.commentAdded.repository_name === variables.repoFullName; }, ),

}

},

};

Notice that the subscription resolver is stateless. You can’t pass in anything. Now, the reconnection logic becomes really messy. You could fire off another query and resubscribe. But this creates a race condition. What if you get a result through the subscription, but the query returns after and overwrites it? While I think this will work for many use-cases, it won’t if you need strong guarantees.

Playing around with this made me realize the second drawback:

2. GraphQL subscriptions only allow one-way data streaming. The client simply cannot send the server any data. This truly limits the possibilities with subscriptions.

This is not to knock GraphQL subscriptions. It is a beautiful abstraction over real-time behavior for any application. Just be aware of these limitations if you are thinking of trying them out. If I got anything wrong, please let me know in the comments :)

Oh, and if you are interested in what we are building. Check us out here.