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.
TL;DR
- 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.
- A
user_online
event 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. - A
user_typing
event 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: App, GraphQL Engine Console and Github.
Introduction
You can build a fully functional realtime chat application without writing a single line of backend code. Hasura GraphQL Engine provides instant realtime 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.
Prerequisites :
- 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.
Data Modelling
Users
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 email
, mobile_number
, location
etc.
The fields last_typed
and last_seen
are to track the user’s activity for functions typing indicators and online users.
Messages
We will store messages in the message
table.
In this case, this table is being used for a single group chat, so it does not have fields like chat_id
, 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 update_message
and delete_message
mutations. The root fields generated by the GraphQL Engine for this table are:
Online users
This chat app also lists down the number of online users. To do this, we will create a view over the users
table.
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.
Typing indicator
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 last_typed_user
.
This view contains the users that have emitted a typing
event less than two seconds ago.
Implementing the front-end
Setting username
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 user_id
and username
.
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
message
table. The query we use is:
On first load, we set $last_received_id
to -1
and 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.
- Now we subscribe to the last message added in the
message
table via the subscription:
- 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
will 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.
Sending messages
Since we have the username
of the user in the state, we can insert messages in the database with a mutation:
Typing Indicator
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 realtime. We simply have to subscribe to view user_typing
and its done.
The subscription is:
Top comments (0)