Building a WhatsApp Clone with GraphQL, React Hooks and TypeScript
Tags: graphql, whatsapp, reacthooks, typescript
Published: 2019-12-17 06:27:10

---
title: Building a WhatsApp Clone with GraphQL, React Hooks and TypeScript
published: true
date: 2019-05-28 14:08:14 UTC
tags: graphql, whatsapp, react hooks, typescript
canonical_url: https://blog.hasura.io/building-a-whatsapp-clone-with-graphql-react-hooks-typescript/
---

## TL;DR

What to expect from this two part tutorial?

- Realtime GraphQL APIs from Hasura
- Authentication with JWT and role based permissions
- [React frontend](https://github.com/hasura/graphql-engine/tree/master/community/sample-apps/whatsapp-clone-typescript-react/react-app); 100% functional components with hooks
- Typescript definitions auto-generated using [GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator)

![](https://blog.hasura.io/content/images/2019/05/WHATSAPP_clone_blog.png)

This is a two part tutorial. The first part will be about building the backend using Hasura GraphQL Engine. The second one will be about building the frontend using React Hooks and Typescript. This is about the backend. The frontend tutorial is coming soon. But the app's source code is available [here](https://github.com/hasura/graphql-engine/tree/master/community/sample-apps/whatsapp-clone-typescript-react/react-app).

Let's start building the backend by deploying Hasura.

[Hasura](https://hasura.io/) is an [open-source](https://github.com/hasura/graphql-engine) engine that gives you realtime GraphQL APIs on new or existing Postgres databases, with built-in support for stitching custom GraphQL APIs and triggering webhooks on database changes.

## Deploy Hasura on Heroku

- Deploy Postgres and GraphQL Engine on Heroku

[![Hasura on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku)

- Get the Heroku app URL (say `my-app.herokuapp.com`) and open it to view the Hasura Console

## Database Modelling

Let's take a look at the requirements for the basic version of WhatsApp where a user can initiate a chat with another user / group.

![](https://blog.hasura.io/content/images/2019/05/relationships.real.large.png)

The core of whatsapp revolves around users. So lets create a table for **users**

Typically the **users** table will have an `id` (unique identifier), `username` and `password` for logging in and metadata like `name`, `picture` and `created_at` for whatsapp's use-case.

Head to the Hasura Console -\> **Data** -\> **Create table** and input the values for users table as follows:

![](https://blog.hasura.io/content/images/2019/03/create_table_users.png)

Here we are ensuring that **username** is a unique column and we make use of postgres default for the timestamp column **created\_at**. The primary key is **id** , an auto-incrementing integer. (you can also UUID).

Now, the next important model for whatsapp is the **chat** table. Any conversation between 2 users or a group will be considered as a chat and this will be the parent table. The model for chat table will have the following columns `id` (unique identifier) , `created_at` and properties of a group chat `name` , `picture` and `owner_id`. Since the chat will not always be a group chat, the columns name, picture and owner\_id are **nullable.**

![](https://blog.hasura.io/content/images/2019/03/create_table_chat-1.png)

The **owner\_id** is a column which decides whether the chat is a private chat or a group chat.

But irrespective of whether the chat is private or group, we need to map the users involved in each chat. Let's go ahead and create a new table **chat\_users** which will have **chat\_id** and **user\_id** as the columns and both would form a composite primary key.

![](https://blog.hasura.io/content/images/2019/03/create_table_chat_users.png)

Now that we have users, chat and their mapping ready, let's finish the missing piece, **message.** The message table will have the following columns; `id` (unique identifier), `content` (message content), `created_at`, `sender_id` and `chat_id`.

![](https://blog.hasura.io/content/images/2019/03/create_table_message.png)

The core database tables required for a simple WhatsApp clone is ready.

### Relational modelling with constraints

We are using Postgres database underneath and hence we can leverage the relational features like constraints and relations.

Let's add foreign key constraints to all the relevant columns so that we can fetch related data in the same query.

Head to **Data** -\> **Chat** -\> **Modify** and create a foreign key constraint for the column **owner\_id** which has to be a value of **users-\>id**

![](https://blog.hasura.io/content/images/2019/03/create_foreign_key_owner_id.png)

Now, lets create foreign key constraint for **chat\_users** table for both columns **chat\_id** and **user\_id**

For example, **chat\_id** column would have a constraint on **chat** table's **id** column. Similarly create a foreign key constraint for **user\_id** column which will point to **users** table's **id** column.

![](https://blog.hasura.io/content/images/2019/03/create_foreign_key_chat_id.png)

Let's move on to **message** table to create the last two foreign keys required.

**sender\_id** :: **users -\> id**

**chat\_id :: chat -\> id**

![](https://blog.hasura.io/content/images/2019/03/create_foreign_key_sender_id.png)

Great! Now we are done with creating the necessary constraints for all the tables.

### Create relationships using console

We need to tell Hasura that relationships exist between two tables by explicitly creating them. Since we have foreign keys created, Hasura console will automatically suggest relationships available. For the foreign keys that we created just above, let's create corresponding relationships so that we can query related data via GraphQL.

We need to create relationships from `chat` to `users` as an object relationship. It is a one to one relationship.

![](https://blog.hasura.io/content/images/2019/05/whatsapp-add-relationship.png)

Similarly we need to create array relationships from `chat` to `chat_users` and `message` tables. A chat can have multiple messages and can involve multiple users.

Using the suggested array relationships from the console, let's create the following chat\_users.chat\_id -\> chat.id and message.chat\_id -\> chat.id

And in `users` table, we create the necessary array relationships for the following:

chat.owner\_id -\> users.id

chat\_users.user\_id -\> users.id

message.sender\_id -\> users.id

![](https://blog.hasura.io/content/images/2019/05/whatsapp-add-relationship-1.png)

## Queries

Hasura gives instant GraphQL APIs over Postgres. For all the tables we created above, we have a ready to consume CRUD GraphQL API.

With the above data model, we should be able to query for all use cases to build a WhatsApp clone. Let's consider each of these use-cases and see what the GraphQL query looks like:

- **List of all chats** - In the landing page, we would like to see the list of chats along with the user information. The chat list should be sorted by the latest message received for each chat.

```
query ChatsListQuery($userId: Int!) {
    chat(order_by:[{messages_aggregate:{max:{created_at:desc}}}]) {
      id
      name
      picture
      owner_id
      users(where:{user_id:{_neq:$userId}}) {
        user {
          id
    	  username
    	  name
    	  picture
        }
      }
	  
    }
}
```

We make use of relationship `users` to fetch relevant user information and filter by user who is already logged in and belongs to the same chat.

- **List of all users/groups** - We would like to also list down all the users or groups that the user belongs to, so that a conversation can be initiated.

```
query ExistingChatUsers($userId: Int){
    chat(where:{users:{user_id:{_eq:$userId}}, owner_id:{_is_null:true}}){
      id
      name
      owner_id
      users(order_by:[{user_id:desc}],where:{user_id:{_neq:$userId}}) {
        user_id
        user {
          ...user
        }
      }
    }
}
```

- **List of messages in a chat** - This query will give a list of messages for a given chat, along with sender information using a relationship.

```
query MessagesListQuery($chatId: Int!) {
	message(where:{chat_id: {_eq: $chatId}}) {
        id
        chat_id
        sender {
          id
          name
        }
        content
        created_at
  	}
}
```

## Mutations

Now that the queries are ready to fetch data, let's move on to Mutations to make modifications like insert, update or delete in the database.

- **Insert chat/group (nested mutation)** - The first time a conversation occurs between two users, we need to make a mutation to create a record in `chat` and create two records in `chat_users` . This can be done using Hasura GraphQL's [nested mutations](https://docs.hasura.io/1.0/graphql/manual/mutations/insert.html#insert-an-object-and-its-nested-object-in-the-same-mutation).

```
mutation NewChatScreenMutation($userId: Int!,$currentUserId: Int!) {
    insert_chat(objects: [{
      owner_id: null,
      users: {
        data: [
          {user_id: $userId},
          {user_id: $currentUserId}
        ]
      }
    }]) {
      affected_rows
    }
  }
```

- **Insert message** - To insert a message, we can issue a simple mutation to `message` table with the relevant variables.

```
mutation MessageBoxMutation($chatId: Int!, $content: String!, $sender_id: Int!) {
    insert_message(objects: [{chat_id: $chatId, content: $content, sender_id: $sender_id}]) {
      affected_rows
    }
}
```

- **Delete chat** - To delete a chat, (either one-one or a group), we can issue a simple delete mutation. But to avoid dangling data, we would issue a delete to cascade all rows from `chat_users` and `message` as well apart from `chat`.

```
mutation deleteChat($chatId: Int!) {
    delete_chat_users(where:{chat_id:{_eq: $chatId}}) {
      affected_rows
    }
    delete_message(where:{chat_id:{_eq: $chatId}}) {
      affected_rows
    }
    delete_chat(where:{id: {_eq: $chatId}}) {
      affected_rows
    }
}
```

**Note:** We could also create an `on delete constraint` in postgres which takes care of this automatically. But the above mutations are shown for demonstration.

- **Update user profile** - Finally, we need a mutation to update user's profile data like the name, profile picture etc.

```
mutation profileMutation($name: String, $picture: String, $userId: Int) {
    update_users(_set: {name: $name, picture: $picture}, where: {id: {_eq: $userId}}) {
      affected_rows
      returning {
        id
        name
        picture
        username
      }
    }
  }
```

## Subscriptions

Now comes the exciting part! Realtime data. We need a way to notify the user when a new message has arrived. This can be done using GraphQL Subscriptions where the client watches for changes in data and the server pushes data to client whenever there is a change in data via websocket. We have two places where we need realtime data. One for new messages, and one for users who are registered.

- **Subscribe to latest messages**

```
subscription MessageAdded {
    message_user {
        id
        chat_id
        sender {
        id
        name
        }
        content
        created_at
    }
  }
```

- **Subscribe to users**

```
subscription UserUpdated {
    users(order_by:[{id:desc}]) {
      id
      username
      name
      picture
    }
}
```

## Permissions and Auth

The Auth API for signup and login is provided by a simple JWT server. The source code for the server is available [here](https://github.com/hasura/graphql-engine/tree/master/community/sample-apps/whatsapp-clone-typescript-react/auth-server).

Now coming to Authorization, Hasura allows you to define [role based access control](https://docs.hasura.io/1.0/graphql/manual/auth/authorization/index.html) permissions model.

In our data model, there is one role for the app and its the `user` role. We need to define permissions for this role for the tables created above. To insert into chat, a user role needs to have the following permission - The user should be the owner of the chat or the user should belong to the chat that they are creating.

![](https://blog.hasura.io/content/images/2019/05/permissions-chat-insert.png)

Similarly for `users` table, insert permission looks like the following:

![](https://blog.hasura.io/content/images/2019/05/permissions-user-insert.png)

We are substituting session variables to match the `id` of the user.

Similarly we can define permissions with the same condition for all operations.

Check out the [metadata](https://github.com/hasura/graphql-engine/blob/master/community/sample-apps/whatsapp-clone-typescript-react/hasura/migrations/metadata.yaml) for the complete list of permissions.

## What's Next?

The app data model currently doesn't have the following features: Typing indicator and read receipts. This can be introduced with minor updates to schema.

Now that the backend is ready, we can move forward with the integration of GraphQL APIs with the React Frontend with Hooks, Typescript and GraphQL Code Generator.

Watch out for this space for the second part of the tutorial involving the frontend!