WebSockets are an outdated technology deemed dead thanks to the ietf not adding HTTP/2 support. Using Server Sent Events, we could simplify the implementation of GraphQL Subscriptions, reduce frontend code and increase performance as well as flexibility when writing modern frontend applications.
GraphQL subscriptions are a mechanism to stream updates from the GraphQL server to the GraphQL client. You can use subscriptions e.g to automatically update the UI once there's a new message available in a chat room. Here's an example Operation:
In order to stream messages from the server to the client, there needs to be a persisted connection between the two. The most used transport for GraphQL Subscriptions is using WebSockets based on the GraphQL over WebSockets Protocol by Apollo.
I'd like to discuss various aspects of this approach and why I think we should deprecate it in favour of technology that is a much better fit, Server-Sent Events (SSE).
To initiate the protocol it's not enough to just open a WebSocket connection. The client needs to send a "connection init" message to which the server needs to respond with "ack". WebSockets operate over TCP so it's not obvious to me why you would need to ack receiving a message.
Once the initial message is acknowledged the client needs to send a GraphQL operation alongside the ID of the operation. Both the client and the server have to remember the ID of each individual Operation because all Operations/Subscriptions get multiplexed over a single WebSocket connection. This makes the implementation of both server and client overly complex. We will see how SSE will improve this because it takes care of the multiplexing for us.
If the client decides to unsubscribe a subscription it needs to send a "stop" message over the WebSocket connection. This gets once again acknowledged by the server sending a "done" message. Again, as this is still using TCP I'm not sure why this is required.
The Browser API to use Server-Sent Events is named EventSource. While WebSockets allow bidirectional communication, Server-Sent Events, as the name indicates only allow the server to stream events to the client and not in both directions. Luckily, we don't have to stream messages from the client to the server to implement GraphQL subscriptions. We simply open one EventSource for each Subscription. This simplifies both the client as well as the server implementation. There's no more custom code required to remember operations and their ID's to associate a message with the right Subscription. Multiplexing is done by the client & server automatically.
The ietf decided to not add WebSocket support over HTTP/2. This means that for each WebSocket connection, the browser has to open a new TCP connection to the server because the initial Handshake is based on HTTP/1.1. Depending on the browser your users are using the number of allowed TCP connections per host varies between 2 and 8 averaging at around 5. Keep in mind that a user might open multiple tabs with the same website so you should try keeping open connections at a minimum.
With Server-Sent Events this is a different story. Server-Sent Events, when used with HTTP/2 multiplex automatically over a single TCP Connection. This means you can open more than 100 EventSources to the same host across multiple tabs and would still only use one single TCP connection.
These days, component-based single page applications get more and more popular. Frameworks like React, Vue, Flutter etc. all have some concept of Components that take data and render it.
Using a persisted WebSocket connection means we have to use dependency injection to make this connection available to all components.
With the EventSource API on the other hand we can simplify the code required to implement Subscriptions. Here's an example using React's Hooks API:
The implementation is rather simple. Once the component get's mounted we start the EventSource. New messages get pushed into setState() to trigger re-rendering the component. The
return () => function at the end of useEffect can be used to clean up the EventSource to avoid memory leaks. No additional library is required to implement multiplexing.
What looks simple on the client is even simpler on the server. In comparison to WebSockets, Server-Sent Events can be implemented without any additional dependencies. Have a look at this Example written in Golang to see how simple the implementation is.
With all the positive aspects what might hold you back from adopting this?
First of all, the EventSource API is supported by 93.8% by all Browsers. The WebSocket API, on the other hand, is supported by 97.41%. That is because IE does not support the SSE API. Is this a blocker? No, but it contradicts with the idea of reducing complexity. If you want to support IE you have to add a polyfill for the missing API.
The EventSource API makes a lot of sense when used together with HTTP/2 because only then you can multiplex all Subscriptions over a single TCP connection. So, if your server or clients don't support HTTP/2 you're in trouble. If you're not sure if your clients can use HTTP/2 you might want to stick with WebSockets.
The non-polyfill Browser API to initiate an EventSource doesn't allow you to send a payload. That is, you have to send the Operation as well as the variables in the query string of the URL. Due to URL length limitations, you might not be able to send the Operation in its original format. This problem is best addressed using persisted queries. I've written another article on persisted queries if you want to get more details about the concept.
At the same time, we're forced to use Persisted Queries and need to migrate from our WebSocket implementation to SSE, don't we? Not really, this is where WunderGraph comes into the picture.
We have done the migration already. WunderGraph takes your GraphQL Server with the WebSocket implementation and turns all Subscriptions into Server-Sent Events on the fly.
We also take care of persisting all GraphQL Operations for you and generate a client for any frontend framework you'd like to use.
This means you get all the benefits of SSE without any additional work to be done.
As a side effect your GraphQL server becomes a lot more secure because it's not directly exposed anymore to the public. It's hidden behind WunderGraph which only allows the persisted you previously registered.
Did you know that the Query Planner of WunderGraph allows you to join Subscriptions with other data sources?
This feature allows you to join any type of different data sources together with very little configuration.
For the future we're planning to add support for GraphQL Federation. This means you will be able to have Subscriptions and many more features for your federated GraphQL implementations thanks to the WunderGraph Query Planner and Execution Engine.
If you're interested in this functionality, leave your email in the chat so we can inform you once we have implemented it.