Building a realtime chat app with GraphQL Subscriptions
Here is how I used subscriptions in Hasura GraphQL Engine to build a full-fledged real-time group chat app on Postgres with React and Apollo.
- Hasura GraphQL Engine provides real-time GraphQL APIs to query PostgreSQL tables.
- Not a single line of backend code is written.
- Subscriptions are used for event notifications and not for fetching data. Whenever an event occurs, a GraphQL query is used to fetch only the new messages. This way we do not re-fetch the same data.
user_onlineevent with the current time-stamp is emitted every two seconds in the form of a mutation. A subscription is used for getting a list of online users that updates realtime.
user_typingevent with the current time-stamp is emitted every time a user types a few characters (in the form of a mutation). A subscription fetches the user that typed last.
- Links: Github.
You can build a fully functional realtime chat application without writing a single line of backend code. Hasura provides instant real-time APIs over Postgres. This means that you can simply create tables and start querying the data in the tables over HTTP (queries and mutations) and Websocket (subscriptions).
This blog talks about building a real-time group chat using GraphQL subscriptions. It is more of a philosophical rant about modelling your data and consuming the data from the front-end. Code subtleties are not covered; if you are interested in seeing the code check out this git repo.
- Knowledge of consuming a GraphQL API i.e. Queries, Mutations and Subscriptions.
- Fundamentals of React.
Although we will talk in the context of a group chat app, you can use these ideas for making any kind of realtime chat application.
To store the users, we will make a user table.
We are not adding auth in this application, but you can if you wish to. You have to insert the user in this
user table whenever a user signs up.If you are adding auth, it can have more fields like
last_seen are to track the user’s activity for functions typing indicators and online users.
We will store messages in the
In this case, this table is being used for a single group chat, so it does not have fields like
is_deleted etc. You can add all these fields for more complex examples
We will be selecting(querying) data and inserting data into this table. This application does not support updating or deleting the messages. You can implement that super easily using
delete_message mutations. The root fields generated by the GraphQL Engine for this table are:
This chat app also lists down the number of online users. To do this, we will create a view over the
This view contains the list of users that had updated their
last_seen less than 10 seconds ago. This view gives us information about online users.
To show whenever someone is typing, we have a
last_typed field in the
user table. We will create a view over the
user table called
This view contains the users that have emitted a
typing event less than two seconds ago.
Implementing the front-end
Since we don’t have auth implemented in this app, we will prompt for username whenever the app loads. This username is stored by inserting it in the
user table as a mutation.
Once the mutation is completed, we render the chat screen with the props
Emitting online events
We are going to render a list of online users in this app. To get the list of online users, we need all users to emit
online events every 2 seconds. This is done in the
componentWillMount() of the root component after entering the username. The users will emit this “online event” in the form of an
update mutation to the
user table where we update the
last_seen column with the current timestamp .
Subscribing to messages
GraphQL Subscription can be used in two ways:
- We can subscribe to a query and render the data that we receive.
- We can treat a subscription as an event notification and run custom logic every time we receive an event.
We will use the second approach. This is because, if there are a lot of messages in the group chat, we do not want to keep receiving them again when we already have them. The algorithm to fetch messages is as follows:
- On first load, we fetch all the messages that exist in the
messagetable. The query we use is:
On first load, we set
timestamp to an ancient timestamp so that all messages are loaded. We also order the messages in ascending order of
timestamp so that we get the messages in the correct order.
2. Now we subscribe to the last message added in the
message table via the subscription:
3. Now, whenever we get the data from the above subscription, we can refetch the messages using the previous query but with new variables. The
$last_received_id will be the id of the latest message in our client state and the
$last_received_ts willl also be the timestamp of the latest message in our client state.
In this way, we fetch chat messages in realtime and we also avoid fetching the same data multiple times.
Since we have the
username of the user in the state, we can insert messages in the database with a mutation:
When the user is typing a message, we emit one
typing event for every few characters that the user types. This typing event is emitted in the form of an update mutation to the
user table by updating the field
last_seen to the current timestamp.
Now that every user is emitting a
typing event every time they type a few characters, we can run a subscription to the last user that typed in the past 2 seconds. Remember, we created a view for this functionality. Also, we don’t want to see ourselves typing, so we filter ourselves out while making the subscription.
Online users list
As we mentioned before, every user emits an
online event every 2 seconds. Using these events, we can render a list of online users that updates real-time. We simply have to subscribe to view
user_typing and its done.
The subscription is: