Build a responsive, offline ready, real time chat with Amplify, AppSync and Apollo.
I was recently challenged by Jan Romaniak, who wrote an article about Working realtime chat in React in less than 20 minutes. It indeed sounds impressive, to be able to build something functional so quickly. I decided to build similar app with different stack, while trying to keep the required time to 20 minutes.
In this post, we’ll create a simplified chat (rooms and messages) with AWS services to store data, authenticate users and provide a GraphQL API. I’m a big believer in “Offline First” as a key characteristic of modern apps. That’s why I couldn't go with base Amplify GraphQL Client and instead, we will go with Apollo Client (and it’s Optimistic UI).
Step 1. Create new React App.
Using create-react-app package create the project:
$ npx create-react-app chatapp
$ cd chatapp
Step 2. Install packages.
Packages worth to mention:
- AWS Amplify, AWS AppSync, Apollo — communication and offline,
- React Router — routing between screens,
- Material UI — make the app look pretty,
- React Web Gifted Chat — everything connected with chat.
$ npm install --save aws-amplify aws-amplify-react aws-appsync aws-appsync-react graphql-tag react-apollo react-router-dom uuid @material-ui/core @material-ui/icons react-native-web react-web-gifted-chat
Step 3. Install AWS Amplify CLI and create/configure Amplify project.
Follow the command line instructions to configure amplify. It will ask you to login to your AWS Account and configure IAM profile. For the initialization follow with values of your choice (my output shown below).
$ npm install -g @aws-amplify/cli
$ amplify configure
$ amplify init
Once project is initialized, we can jump to the fun part. As mentioned before, Amplify CLI let’s you play with various AWS services. With few clicks you can extend application with object storage (Amazon S3), notifications or analytics (Amazon Pinpoint and Amazon Kinesis). Here is the list of available services:
| Category |
| ------------- |
| analytics |
| api |
| auth |
| function |
| hosting |
| storage |
| notifications |
It’s about the time to build our GraphQL API (on top of AWS AppSync). The process will guide us through series of questions, that will leave us with fully functional API, Authentication and data storage.
$ amplify add api
Chose the default answers for prompted questions (except for Authorization type choose Amazon Cognito User Pool). Also keep in mind that you can play with custom values (such as default password policy). Stop at the step where you will be asked to edit the schema file (Press enter to continue). Your output should look like one bellow:
The schema in AWS Amplify is defined with GraphQL Transform. I do recommend reading their introduction article first, to get familiar with the concept and available Directives. Our initial schema defines Room and Message models, that have defined named connection (bi-directional relationship). Moreover we specified sortField, that will be used as GSI (Global Secondary Index) in DynamoDB. It’s essential for our case to set custom key, as by default data is spread by Primary Key hash to different partitions. Here is a great article that explains role of partition key and sort key in DynamoDB.
Replace default schema with one shown above. Then save the file and Press Enter in the terminal. It will finish the API initialization step.
Step 4. Push API and Auth to the cloud.
$ amplify push| Category | Resource name | Operation | Provider plugin |
| -------- | --------------- | --------- | ----------------- |
| Auth | cognitodd0c8d9f | Create | awscloudformation |
| Api | chatApp | Create | awscloudformation |
? Are you sure you want to continue? YesGraphQL schema compiled successfully.
Do you want to generate code for your newly created GraphQL API No
- Updating resources in the cloud. This may take a few minutes...
This schema will be now transformed to standardize GraphQL schema format. This part will take a while, so feel free to grab a cup of coffee ☕.
Step 5. Build the UI layer in React.
Let’s start with the core of the app, without the communication yet. We’ll start by adding Router to the App.js and creating Rooms and Chat component.
Room Component for now is a base Material UI List, that iterates over ROOMS list.
The last part we need is Chat Component. Here as mentioned in the intro, I will use React Web Gifted Chat, as it has everything that we need out of the box.
You can play with what we did so far on CodeSanbox.
Step 6. Connect with AWS AppSync through Apollo.
We have to configure Apollo Client with values from aws-exports.js. This file was generate during the Amplify API initialization process. Worth to mention is that all we have to do, in order to have User authentication is to use withAuthenticator HOC. The rest of the code is self explanatory. Follow this documentation to read more about AWSAppSyncClient.
Everything is configured, when the App will reload, you will see orange login screen. You have to create an account there and login to access the app. You can manage users through AWS Console (Cognito User Pool).
Now it’s time to write first queries to GraphQL server. To do that, let’s start by exploring the API. Open AWS Console and navigate to AppSync. Then select the one you just created from the list and navigate to “Queries” tab.
You can play around with it, creating new Rooms and listing them. If you are not familiar with GraphQL read this article first. Let’s modify our Rooms Controller to use Apollo Query to list all the rooms. Query component with attribute cache-and-network will let us use it offline mode. Detailed Query documentation is available in Apollo docs.
By now, you should be able to see the empty list. We haven’t created any room yet. Mutation is a bit more tricky, especially when we deal with cache. The concept is very well explained here. Please read it first, and then follow the article. We have to specify Optimistic Response and update function, to be able to use app in offline mode.
One of the core function of chat is to receive messages in real time, and it’s something GraphQL allows us to do with subscription. Due to the complexity of Chat Component, we will split it into two Chat and ChatWithData. It’s a naming convention, where component WithData is aware of data implementation.
One note about subscription in AWS AppSync. By default AppSync will create onCreateMessage, onUpdateMessage and onDeleteMessage (associated with mutations). This behavior works fine with most of the cases, however here, we want to subscribe only to messages from room we are in. To do that, we will apply subtle change in our GraphQL schema. Model directive allows to specify queries, mutations and subscription. We will overwrite it to build custom onCreateMessage with roomId param. Subscription will be resolved on the server, to match the param. The data is fed from the parameters we requests in createMessage mutation — make sure roomId is there!
It’s time to finally update Chat and newly created ChatWithData. Let’s start with ChatWithData Component.
I have created helpers.js for user injection as a prop. Amplify Auth allows to retrieve current authenticated user, but due to it’s async nature (data can be cached or pulled from the server) it can not be executed in render function.
Chat Component requires only subtle changes — instead of using local constant, we will use props.
Finally, change Route in App.js to use ChatWithData instead of Chat.
Step 7. Final touches (optional)
With current schema each user can update and delete all the messages. AWS Amplify can easily solve this problem with one change in the schema. By adding auth directive we can protect object by a set of authorization. Our final GraphQL schema with auth and custom subscription looks as follow:
Step 8. Working offline Chat App 🎉
Hopefully you manage to stay through this article and did it in less than “20” minutes 😉. It’s also worth to mention that Amplify is constantly evolving. Some of the features such as sortField is still not yet documented, even though it’s in the code for a while. There is a bunch of cool features scheduled to be developed in future. As mentioned in the beginning of this article, we just touched the tip of the iceberg. Other services such as AWS S3, could be easily added to this project to accommodate images/views in the conversation.
Thanks for reading! ❤️