Full Stack Serverless - Building a Real-time Chat App with GraphQL, CDK, AppSync, and React
Tags: graphql, serverless, javascript, awsamplify
Published: 2020-08-19 13:41:08

---
title: Full Stack Serverless - Building a Real-time Chat App with GraphQL, CDK, AppSync, and React
published: true
description: How to build a full stack chat app on AWS with GraphQL, React, AWS CDK, AWS AppSync, Amazon DynamoDB, and Amazon Cognito.
tags: graphql, serverless, javascript, awsamplify
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/u059sxjgqs3og0k2ew9q.jpg
---

> Cover image by [Scott Webb](https://unsplash.com/@scottwebb)

In this tutorial you will learn how to build and deploy a real-time full stack cloud application to AWS using [CDK](https://aws.amazon.com/cdk/), React, GraphQL, and [AWS AppSync](https://aws.amazon.com/appsync/).

The app will include authentication, database, GraphQL API, and front end all deployed to AWS via CDK written in TypeScript.

> To see the completed app, check out the repo [here](https://github.com/full-stack-serverless/cdk-appsync-chat)

---

One of the most powerful things about [Full Stack Serverless applications](https://github.com/full-stack-serverless/manifesto) is the ability to share and deploy scalable full stack apps to the cloud in only a few minutes.

This is enabled using a combination of [Infrastructure as Code](https://en.wikipedia.org/wiki/Infrastructure_as_code)(IAC) and decoupled frontends integrated into an end-to-end solution.

In the past, the barrier to entry for IAC has been fairly high, making it hard for traditionally front-end developers or developers not familiar with the cloud to get started using it.

We are now seeing tooling like AWS CDK and the Amplify CLI making it easier for developers to get started building cloud applications with IAC using their existing skillset.

When I say __use their existing skillset__, I'm assuming that the typical front end or full stack developer is familiar with the following:

1. JavaScript, TypeScript, or Python
2. Interacting with a CLI
3. A basic understanding of interacting with RESTful or GraphQL APIS

### Amplify vs CDK

If you've read any of my posts in the past few years, you've probably seen me talk about the [Amplify CLI](https://docs.amplify.aws/cli). The Amplify CLI generates and manages IAC for you under the hood using a category-based approach. CDK on the other hand allows you to use programming languages like Python, Typescript, Java, and C#/. Net to build cloud infrastructure.

Because Amplify also has client-side libraries that are not exclusive to the CLI, you can use CDK with Amplify to build full stack cloud applications.

In this tutorial, this will be our stack:

1. React for the Single page application
2. CDK written in TypeScript for the infrastructure
3. Amplify libraries to handle API calls from the client-side code.

## Getting started

To get started, you will first need to [install](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) and [configure](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-config) the AWS CLI.

Next, [install the CDK CLI](https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_install):

```sh
npm install -g aws-cdk
```

### The CDK CLI

Once you have CDK installed, you should be able to run `cdk` from your terminal and see a list of available commands.

Here are the commands you will probably be using the most:

__init__ - Initializes a new project
__deploy__ - Deploys the infrastructure to AWS
__diff__ - Compares the specified stack with the deployed stack and give you feedback about changes that will be made the next time you run `deploy`

Typically the workflow will be something like this:

1. Initialize a new project with `init`
2. Write some code
3. Run `cdk diff` to see what will be deployed / changed the next time you deploy
4. Run `deploy` to deploy the updates

## Creating the CDK project

First, create a folder that you'd like this project to live:

```sh
mkdir cdk-chat

cd cdk-chat
```

Next, you can create the CDK project using the `init` command:

```sh
cdk init --language=typescript
```

> The language flag can be either `csharp`, `java`, `javascript`, `python` or `typescript`

Once the project has been created you should see a bunch of files and folders created for you. The main code for the project will be in __lib/cdk-chat-stack.ts__.

In order for us to use certain AWS service constructs in our project, they first need to be installed. This project will be using the following services:

1. Amazon DynamoDB (database)
2. Amazon Cognito (authentication)
3. AWS AppSync (GraphQL API, real-time)
4. AWS IAM (managing IAM permissions)

Now, let's install the libraries necessary for these services using either npm or yarn:

```sh
npm install @aws-cdk/aws-appsync @aws-cdk/aws-cognito @aws-cdk/aws-dynamodb @aws-cdk/aws-iam
```

> Once you have installed these dependencies, check your `package.json` file to make sure that `@aws-cdk/core` is the same version of these packages. If not, I would bump `@aws-cdk/core` up to the same version of the installed packages and run `npm install` again.

## Defining the GraphQL Schema

Now that the CDK project is created, we'll need the GraphQL schema that describes the chat app. In the root of the CDK project, create a new folder called __graphql__ and a new file called [__schema.graphql__](https://gist.github.com/dabit3/6762eed3f7ac098756e67abdc74cfdb2) in this folder. In this file, add the following schema:

{% gist https://gist.github.com/dabit3/6762eed3f7ac098756e67abdc74cfdb2 %}

The main things to take note of here are the following:

1. GraphQL types for `Room` and `Message`
2. Typical  `Mutation` and `Query` definitions for basic operations like getting a room by ID, listing rooms, and a query for listing messages for a room by ID
3. Subscription definitions for `onCreateRoom` and `onCreateMessageByRoomId`. By decorating a subscription definition with `@aws_subscribe`, AppSync will automatically create the subscription in the service. By passing in an array of mutations, we can specify what events we want this subscription receive notifications for.

## Writing the CDK code

> The final CDK code is located [here](https://gist.github.com/dabit3/c13bac2e61adcef4fd5e4d77385a9370) for your reference, but we will be walking through it step by step below.

Now that the Schema is created we can begin defining our CDK code.

Next, open __lib/cdk-chat-stack.ts__. At the top of the file, add the following imports:

{% gist https://gist.github.com/dabit3/3f3868e3152f6bc583fa82ee75473526 %}

You can either import the top level construct of the API you'd like to use or you can import the individual APIs themselves. In this case, we've imported the individual APIs from each of the libraries.

### Creating the authentication service

Next, we'll begin creating the services. First, we'll create the authentication service using Amazon Cognito. To do so, add the following code in the `constructor` below the call to `super`:

{% gist https://gist.github.com/dabit3/c60f17b26364a9ab754c12fc9ad59f9e %}

> `CfnOutput` is a way to print out useful values that you will need, in our case we will be using these values in the client-side application.

This code has created the authentication service and configured a few things:

1. Enable users to sign themselves up by setting `selfSignUpEnabled` to `true`
2. Send an email notification for MFA on sign up (`autoVerify`)
3. Defined the required sign up attributes (email)
4. Created a client ID for you to use on the React client

After saving the file you should be able to now see the infrastructure that will be created by running the `diff` command from the CLI:

```sh
cdk diff
```

### Creating the DynamoDB tables and GSIs

Next we need to create the two DynamoDB tables and also configure a GSI (Global Secondary Index) to enable the querying of messages by Room ID.

We'll also need to give permission to DynamoDB to allow querying on the global secondary index using IAM.

{% gist https://gist.github.com/dabit3/3a029c93d22f5def1d8b0c72d30e817f %}

This has created two DynamoDB tables (__CDKRoomTable__ and __CDKMessageTable__) as well as a GSI (`messages-by-room-id`) on the message table.

### Creating the AppSync API

Now that the authentication service and Database tables are configured we can create the API. AWS AppSync is a managed GraphQL service and what we will be using for the GraphQL API.

When creating the API definition we need to map the mutations, queries, and subscriptions that were created in the schema to GraphQL resolvers.

There are two main ways to create resolvers using CDK and AppSync:

1. Writing the resolver as a string using `MappingTemplate.fromString`
2. Using a predefined template (available templates listed [here](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-appsync.MappingTemplate.html)).

In our app we will be doing both.

{% gist https://gist.github.com/dabit3/d04ba7c141600f2f5c8dc0ae1dbc43ce %}

### Deploying the back end

That is all the code we will need for the back end. You can now deploy everything by running the `deploy` command:

```sh
cdk deploy
```

Before deploying you will be prompted with some out put that looks like this:

![CDK Deploy output](https://dev-to-uploads.s3.amazonaws.com/i/27k0pk0xuj9ccvynw88q.png)

Once you deploy the back end you should see some output that looks like this:

```sh
Outputs:
CdkChatStack.UserPoolClientId = 6lcq9gl36cugj6ttq8eqh5cf9m
CdkChatStack.UserPoolId = us-east-1_7xli2V7Oq
CdkChatStack.GraphQLAPIURL = https://57vcrggstrf3xnve4c7isekyea.appsync-api.us-east-1.amazonaws.com/graphql
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/g191c6nyld2q40zznhxw.png)

These values are the result of `CfnOutput`. You will be able to use these values to connect to the client application.

## Client app

This tutorial comes paired with a completed front-end that you can now integrate with your back end.

We will also walk through the individual Amplify APIs for interacting with the back end for user authentication and interacting with the GraphQL API.

### Using the pre-built client app

Clone the React chat app into your project and changed into the new directory:

```sh
git clone https://github.com/full-stack-serverless/react-chat-app-aws

cd react-chat-app-aws
```

Next, install the dependencies:

```sh
npm install

# or

yarn
```

Next, rename `aws-exports-example.js` to `aws-exports.js`.

Finally, populate the properties in `aws-exports.js` with the values output by the CDK CLI:

```js
// aws-exports.js
const config = {
  Auth: {
    region: "us-east-1", // or your region
    userPoolId: "your-userpool-id",
    userPoolWebClientId: "your-client-id"
  },
  aws_appsync_graphqlEndpoint: "your-graphql-endpoint",
  aws_appsync_region: "us-east-1", // or your region
  aws_appsync_authenticationType: "AMAZON_COGNITO_USER_POOLS"
}

export default config;
```

> To see the values output from `CfnOutput` by the CDK CLI at any time, you can run the `cdk deploy` command (even with no changes).

Finally, run the app:

```sh
npm start
```

### Understanding the API calls

Next, let's take a look at how we are connecting to the back end via the client-side application.

### Authentication

To authenticate you can either use the React UI Components or the `Auth` class.

#### UI Components

You can use [Amplify React UI Components](https://docs.amplify.aws/ui/auth/authenticator/q/framework/react) for creating a basic authentication flow.

For example the `withAuthenticator` and `AmplifyAuthenticator` components can stand up an entire authentication flow in just a few lines of code:

```js
import React from 'react';
import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';

const App = () => (
  <div>
    <AmplifySignOut />
    My App
  </div>
);

export default withAuthenticator(App);
```

#### Auth class

The `Auth` class has methods for doing most typical identity management operations like signing up, signing in, MFA, and managing password recovery.

To sign a user up you can use the `signUp` method:

```js
import { Auth } from 'aws-amplify';

await Auth.signUp({
  username: "dabit3",
  password: "MyCoolPassword",
  attributes: { email: "you@yourdomain.com" }
});
```

Check out the documentation [here](https://docs.amplify.aws/lib/auth/emailpassword/q/platform/js) to see the entire flow of signing up and signing in.


### API

To interact with the GraphQL API we will be using the `API` category.

__Sending a query__

```js
import { API } from 'aws-amplify';
import { listRooms } from './graphql/queries';

const roomData = await API.graphql({
  query: listRooms
});
```

__Sending a query with variables__

```js
import { API } from 'aws-amplify';
import { createRoom } from './graphql/mutations';

await API.graphql({
  query: createRoom,
  variables: {
    input: {
      name: "Cool cats"
    }
  }
})
```

__Real time - GraphQL subscriptions__

Subscriptions can be set up to listen to a general mutation (create, update, or delete) or they can be set up to take in arguments.

An important part of using GraphQL subscriptions is understanding when and how to use arguments because subtle changes enable you to modify how and when clients are notified about mutations that have occurred.

For a chat app, it makes a lot of sense to be able to just subscribe to new messages in a single room for example. To make this work, we can pass in the room ID for the messages we'd like to subscribe to.

Here's an example of both scenarios, which is how we are managing them in the app.

__Listening for updates when a room is created and handling it in real time__

```js
import { API } from 'aws-amplify';
import { onCreateRoom } from './graphql/subscriptions';

API.graphql({
  query: OnCreateRoom
]})
.subscribe({
  next: roomData => {
    // roomData is the data from the mutation that triggered the subscription     
  }
})
```

__Listening for updates when a message in a particular room is created and handling it in real time__

```js
API.graphql({
  query: OnCreateMessage,
  variables: {
    roomId: id
  }
})
.subscribe({
  next: async messageData => {
    // messageData is the data from the mutation that triggered the subscription
  }
})
```

Check out the documentation [here](https://docs.amplify.aws/lib/graphqlapi/getting-started/q/platform/js) to see more details around how to interact with a GraphQL API using the `API` class.

---

The CDK API covers a very large surface area, enabling you to do quite a lot in a much more succinct way than traditional infrastructure as code. To learn more about additional CDK APIs, check out the documentation [here](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-construct-library.html).