“Client-side only” realtime web applications with Firebase, GraphQL and apollo-client 2.0
TL;DR When you need to work with GraphQL locally (you can’t or doesn’t want to have a GraphQL server) you can use apollo-link-webworker
to use a WebWorker as the graphql “server” and even generate proper GraphQL subscriptions from external events source, like Firebase Realtime Database events. You can implement this yourself with the apollo-link-webworker package and you can test the final result here : https://firechat-169ea.firebaseapp.com/
This article requires that you are already familiar with apollo-client 2.0, graphql, and firebase.
So you want to create an amazing web application with real time support and don’t want to struggle with server-side code / maintenance. Let’s say you want to build a realtime chat application and you heard about firebase realtime database. “Scalable real time syncinc for JSON data”, sounds good :
- you don’t need a server, you can be focused on the front
- you don’t need to manage the socket connections on the client, firebase does it for you
- the Firebase Realtime Database Rules might not be the most robust way to handle permissions but it’s here, it works, and it’s quite simple to configure
- the Firebase Authentication system lets you implement one of the authentication providers (Google, Twitter, Facebook, Github, or even your custom OAuth implementation) included in the SDK
Firebase sounds like a very good choice, it solves many backend issues but what about the frontend part ? You don’t want to struggle with state management, data fetching, caching, etc. You heard about GraphQL, it seems to perfectly suit your needs :
- you can use a graphql client (relay, apollo, etc.) to handle all the fetching / caching issues
- the UI decides what is needed to fetch, and only what is needed, no more overfetching / underfetching
- you can mock your graphql schema during the early stage of development
Since you already know GraphQL, you’re maybe wondering where is the GraphQL server in this stack ? We do not want to manage any server, but the typical graphql stack involves a graphql client on the client side, talking to a graphql server (apollo server for example) :
How could we create a graphql “server” on the client side ? And what about realtime update ? How can we use firebase socket to generate some kind of graphql subscriptions ? Let’s see that :)
Using a Web Worker as the graphql “server” and subscriptions handler
In the typical graphql stack with apollo-client, apollo-server and a websocket server, here is how the realtime feature is achieved :
- client A opens the chat application, a graphql subscription query is sent to the websocket server to subscribe this client to the pubsub channel that will receive the future results
- client B posts a new message, a graphql mutation query is sent to the apollo-server, which in turns publishes the result in the pubsub channel mentionned above
- the websocket server resolves the subscription and sends the data to all clients listening for changes in this channel
- client A receives the new message
With Firebase, we do not own the websocket server used to communicate real time updates. So we need to generate the graphql subscription on the client side, in the webworker. In the next list, the webworker A refers to the webworker instanciated in the client A’s browser, and webworker B to the webworker instanciated in the client B’s browser.
- client A opens the chat application, a graphql subscription query is sent to the webworker A to subscribe this client to the pubsub channel that will receive the future results. In the meantime, a socket is opened by the Firebase SDK between client A and the firebase realtime database
- client B posts a new message, the graphql mutation is sent to the webworker B that resolves the mutation by pushing a new message in the firebase realtime database.
- client A receives the update from firebase through the socket opened by firebase, this result is used to resolve the graphql subscription and send the resolved result to the correct pubsub channel, thus making the UI to update !
The good news is that the new apollo-client@2.0.0 beta version makes this easy to implement thanks to Apollo Links ! We are going to create a Link that, instead of communicating with a server, communicates with a webworker. Webworker are asynchronous, that’s perfectly fine for subscriptions but for “classical” server communication (queries, mutations, …) we do expect to receive a response via a promise, not via a delayed event. Here’s what we gonna need :
yarn add apollo-client@beta apollo-link@beta apollo-cache-inmemory@beta promise-worker firebase graphql-subscriptions subscriptions-transport-wsyarn add --dev worker-loader
The first packages are related to apollo-client@2.0.0, no more networkInterface
is needed, instead you need an apollo-cache
instance and an apollo-link
instance. If you want to learn how to migrate, it’s here : What’s coming in Apollo Client 2.0
We also need the graphql-subscriptions
package to use the PubSub
object and the subscriptions-transport-ws
package to use the SubscriptionClient
.
The worker-loader
is very usefull to load worker file through webpack.
Implementing the Apollo WebWorker Link
We want to communicate with our webworker via promises when operating a query
or a mutation
and we want to subscribe to a pubsub via the webworker when operating a subscription
. So, let’s implement a PromiseWorkerLink
and a SubscriptionWorkerLink
. The final WebWorkerLink
will be created from a “splitted” link, a way to conditionnaly use one or the other depending on a predicate (in our case if the operation is a subscription
)
The PromiseWorkerLink
constructor only takes one argument : a worker object (we will create this object later, keep on). The sole purpose of the request
implementation is to pass to the PromiseWorker
the current operation and return the response to simulate the client <> server communication.
That was the easy part, let’s see how to implement the SubscriptionWorkerLink
now. The idea behind this link, is to use the SubscriptionClient
exposed by the subscriptions-transport-ws
package. This class is expected to be used with a socket server, through the socket implementation you want (Socket.io, websocket, etc.). You just pass the socket implementation you want to use as a third parameters of SubscriptionClient
.
Let’s use this to implement a “fake” socket implementation that instead of communicating with a real socket server, communicates instead with the webworker. To do so, we must implement the send
method, the onerror
setter and the onmessage
setter. We also have to define some static constant that are read during the process in the subscriptions-transport-ws
package. Just for integrity reason, we also accept the same two parameters in the constructor url
and protocol
even if this is not relevant for us :
Let’s explain this a little bit :
- The
SubscriptionWorkerLink
takes a worker as an argument (it’s the same worker object than before, we still do not know how to create this object, but keep on). - We then instanciate the
SubscriptionClient
. The first and second arguments are not relevant for us (the server url for the first, and the socket options for the second). The last argument is the implementation of our “webworker socket server”. - The
request
method only delegates the request to thesubscriptionClient
instance (internally, the client handles the subscriptions, incoming message and the result to send back). - The
WorkerInterface
just acts as a “fake” implementation of a socket server. Thesend
method is the method called by theSubscriptionClient
instance when it wants to send a message to the socket server, in our case we just have to post a message to the webworker instead. - The
onmessage
setter handles the subscription results. We have to check for thetype
of the received message data.SubscriptionClient
set this toMessageType.GQL_DATA.
With these two links, we can generate the final link with ApolloLink.split
:
Implementing the WebWorker file
The next step is to implement that worker
object that we are talking about since the beginning. The webworker acts as the graphql “server”, it handles the execution of incoming requests. We can simulate the “server-like” communication by using promises. For the “socket server like” communication it’s “easy” : we just have to communicate through natural webworker events by publishing in the pubsub trough async iterator. The code below is extracted from my repository : apollo-link-webworker so if you don’t understand everything, it’s not a big deal (the code is mostly extracted from the subscriptions-transport-ws package from apollo) :
registerPromiseWorker
registers the current worker as a promise worker, it enables the possibility to communicate with this worker through promises. We use this to simulate the client <> server communication.
The onmessage
implementations is here to simulate the websocket client <> websocket server communication. Notice that this worker handles both roles : the worker is the “websocket client” and also is the “websocket server”.
When queries or mutations are handled, the execute
from the graphql package is called, and when a subscription is handled, the subscribe method is used that does all the “magic” by resolving incoming request to real result through the graphql schema (we are going to talk about it soon).
Notice the self.postMessage
on line 40. The message we are posting here is the result of the resolved subscription. This is then received in the WorkerInterface.onmessage
implemented above to pass the result to the registered handler (the client cache update logic, from react-apollo for example, we’re going to see that later, keep on ;) ).
The pubsub
is an instance of PubSub
class from the graphql-subscriptions
package :
The GraphQL part : schema, context, firebase models and connectors
Let’s put aside the implementation details of the webworker link and all the stuff we mentionned above to speak about our real application logic : the GraphqlQL schema, context, and firebase database communication !
For the rest of this post, I assume you’re already familiar with GraphQL, firebase, apollo-client and react.
The firebase database
Our firebase database modeling our chat application has this shape :
As you can see I keep it to the very minimum, a message has a content
, id
, and a userId
field.
A user only hasid
and username
fields.
And this is the UI (yeah, I know…) :
The GraphQL schema
Nothing special to say here, if you’re familiar with graphql nothing should be scaring you right now. We’re gonna talk about the resolvers in a moment. The most interesting part here is the Subscription.messageAdded
resolver where we use the asyncIterator
on the pubsub
instance, iterating over the OnMessageAdded
channel results. OnMessageAdded
will be the operation name we’re gonna use for our messageAdded
subscription.
The models
Our models are a very thin layer that exposes an api used by the resolvers :
Let’s implement the firebaseConnector
object. We use dataloader
to save some bandwidth (it does get very important when you are on a paid plan because it escalates very quickly) :
The firebase connector
We export a simple createFirebaseConnector
factory that just accepts an instance of the firebase realtime database object. Since we have access to the database object, we decide to link here the realtime update received from firebase to our subscription system through the pubsub
instance.
The context
Tying it all together
Ok we now have all the necessary pieces, we must now tie it all together by instanciating an apollo-client
instance :
And we’re done !
You can now use this apollo-client
instance like you would with any other client instance ! Let’s see an example with the MessageInput
component I used to handle the message submission in the UI screenshot and the MessagesList
component that handles the message list and subscriptions to new messages :
MessageInput
MessagesList
The OnMessageAdded
operation’s name is the name used through the whole pipe.
Introducing apollo-link-webworker
Since you don’t want to write all this boilerplate every time (and neither do I), I wrote a package, apollo-link-webworker
,that exposes a createWebWorkerLink
factory to build this Link, and some utility functions to help you build your own worker. It’s still in early alpha, not tested, not to be use in production, but contribution are very welcomed !
Example app with firebase authentication
You can see the chat application I’m talking about since the beginning of this post on this repo : firechat repository. The application is online here : https://firechat-169ea.firebaseapp.com/ (you’ll need to accept popup for the authentication to work properly)