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.

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 emitteda 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:

  1. We can subscribe to a query and render the data that we receive.
  2. 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:

  1. 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.

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.

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 onetyping 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:


Hasura is an open-source engine that gives you realtime GraphQL APIs on new or existing Postgres databases, with built-in support for stitching custom GraphQL APIs and triggering web hooks on database changes.