GraphQL over websockets doesn't seem to be widely used yet. There are several libraries, articles and docs available but half of them don't work, require additional new technologies or provide non-standard solutions. I tried to find a simple, reliable and scalable solution that didn't need much more knowledge outside of Apollo, GraphQL and WebSockets. I also don't want it to rely on unrelated libraries like Express so I don't lock myself in. It will probably also benefit the server structure to keep Apollo separate from other modules (sort of like a microservice).
Using apollo-server
(while not very performant) seems to be one of the few proven technologies for having a GraphQL backend in Node.js. Using that on the server is probably going to provide the quickest road to production, as it's widely used it will probably have decent documentation and issues can probably be solved by a few Google queries. It seems that its subscription system has solutions built-in for future scaling through withFilter, i.e.. load balancing multiple Node.js servers handling socket connections with a shared message queue behind them. It seems a bit cumbersome to write subscriptions, but the scalability is probably going to be worth it. It does mean having to introduce a message queue to my stack, but hopefully that complexity can be delayed until after I get 1000+ users.
Unfortunately subscriptions-transport-ws
seems to have a bunch of problems. First of all it has a big "Work in progress!" warning in the readme, suggesting it's not ready for production yet. It also hasn't had any real commits since March 2019 and there are questions about the package's maintenance status. Basically it seems to be discontinued before it ever was finished. Due to these reasons I would recommend avoiding this library if possible.
The Apollo Server documentation comes to our rescue with a section on subscriptions. The setup actually works for queries/mutations over WebSockets too. It provides a regular HTTP endpoint as well. This makes introspection possible, and it allows us to consider running login over HTTP instead of websockets. This may make it easier to recognize users and hopefully make the solution less viable to DOS attacks. This is the setup implemented below.
For the client-side apollo-client
is proven technology I know will work well, so is used as a base. To make it work over websockets we need apollo-link-ws
which unfortunately uses subscriptions-transport-ws
under the hood. Sadly I can't find a better alternative at this time. I decided to stick with it anyway since it is used regularly enough and if it works, I guess it works.
The WebSocket when open will send messages as shown in the image below. Note that these are internals of apollo-link-ws
and apollo-server
, so technically you don't need to know about this.
The code below shows a proof of concept of this combination. I am guessing it will support up to 5000 simultaneous connections out of the box, but I'll be running tests to verify this long before reaching a 1000.
Relevant urls:
- apollographql/subscriptions-transport-ws#729
- https://www.apollographql.com/docs/react/data/subscriptions/
- https://www.apollographql.com/docs/link/links/ws/
- https://itnext.io/how-we-manage-live-1m-graphql-websocket-subscriptions-11e1880758b0
- https://medium.com/@alexhultman/millions-of-active-websockets-with-node-js-7dc575746a01
- https://github.com/benawad/node-graphql-benchmarks
- https://ma.ttias.be/benchmarking-websocket-server-performance-with-artillery/
https://github.com/enisdenjo/graphql-ws/ is the go-to solution nowadays