Real time updates in your React app, with Amazon WebSocket API Gateway / AWS Lambda / MongoDB — Part 1
Amazon announced the arrival of WebSocket APIs a while back, available via their API Gateway service. This is great news for those of us already implementing WebSockets or any near real-time features into our applications. The service is a great tool to gain real-time access to not just the API Gateway but to other AWS offerings, such as Kinesis, SNS and more. The possibilities are endless!
Now instead of just showing you how to set up the WebSocket API Gateway, we’ll build a basic chat app with React, to show the entire flow of data, browser to server-less (or lambda in our case), and back. Let’s take a look at the architecture of our basic chat app.
In Part I we won’t go into the details of building the actual React app. Here we will set up our AWS stack via the AWS dashboard. Get our lambda talking to our EC2 and WebSocket API Gateway. In case you are interested in how to use the Serverless framework to do this, stay tuned.
Our AWS Services
Let’s take a quick look at the AWS services we will be using for this example. This is just a brief introduction to these services, in case you are unfamiliar with any of them, links have been provided to allow you to explore them further. I’ve added pricing information, particularly Free Tier eligibility to allow you to follow along without paying any money.
Amazon API Gateway — An AWS service that allows you to set up a REST or WebSocket APIs that can be accessed from anywhere. The API Gateway provides easy access to other AWS services as well, making it the perfect gatekeeper for your application’s API.
Pricing: Amazon API Gateway is available in the Free Tier and allows 1 million calls per month.
AWS Lambda — Lambda functions are the magic ingredient that let you compute things without setting up and maintaining servers. The number of events that can trigger your lambda code is one of the highlights of this service, allowing easy integration with any AWS configuration.
Pricing: AWS Lambda is available in the Free Tier, with 1 million free requests per month (around 3.2 million seconds of compute time)
Amazon EC2 / MongoDB— Our MongoDB will sit inside an Amazon Elastic Compute Cloud (EC2) instance, which is a web service that provides computing capacity on demand.
Pricing: Amazon EC2 is also in the Free Tier, offering 750 Hours per month for free.
Setting up AWS
Now that the introductions are over, let’s get down to business. We begin with our WebSocket API Gateway. Sign in to your AWS Console, and choose the API Gateway service. Click on the big blue button to + Create API. You’ll see the screen below:
Note: This article assumes some familiarity with Amazon API Gateway and it’s key concepts. If that does not apply to you, check out their documentation here.
Start by selecting WebSocket as the Protocol for your API. Enter a name for your API Gateway, and then a special variable that will act as the route key. This is the variable you will use to let the API Gateway know which route to take. For our app, I chose:
$request.body.rcaction
After your API Gateway is created, you will land on the dashboard of your new API. Let’s quickly go through the Routes available to you here.
- $connect — This is the route every new connection takes. Before any other route is picked, a new connection is established through this route. This is where we will add connection IDs to our Redis store through our Lambda.
- $disconnect — As the name suggests, whenever a connection is lost, or disconnected this route is called. It is important to note that this does not happen with every connection. You will need to add some checks in your lambda, as we will see later, to check for stale connections. But this is still a safe place to add your disconnection logic.
- $default — This route is a catch-all. Any requests for which the route selection expression cannot be evaluation, or with no matching routes, end up here.
- Custom Route — You can enter as many custom routes, picked based on the route key, as you want. All you need to do is set up the integration option for each of your custom routes. We will use our lambda function here.
In order to set up our routes we will have to make a quick detour to our AWS Lambda function. Since we’re using the AWS Console to set up your AWS stack, there are some limitations. We will need to set up the integration first, and then set up the route.
But before we dive into the lambda code, let’s take a closer look at what the API gateway will send our lambda function. The event
object, sent by the API Gateway integration request will hold the data we want. Note, we chose LAMBDA_PROXY
as our Integration Request type, so this sends us our event object as is. Here’s a look at the $connect
and $disconnect
event objects.
The keys we are interested in for this lambda are :
{
requestContext: {
routeKey,
connectionId,
domainName,
stage
}
}
Notice how there is no body
key. These routes have no body since they are not called by a client directly, these are routes called when a WebSocket connection is successfully completed / disconnected.
If you’re wondering about the rest of values, read this to understand how the API Gateway deals with Lambda functions.
Finally it’s time for our Lambda. You can see the code for my lambda connect-lambda
below. To see all the code, check out this Git repository. I have set up handlers for all the routes, with only my test route doing the actual POSTing of messages to all connected clients.
In connect / disconnect routes, I’m simply adding the connection Ids to my MongoDB database and deleting them respectively. We’ll use the room
variable once the React app is set up. The most important route here is the test
route.
Now if you’re wondering about the patch.js
file, this is the workaround that allows me to use the ApiGatewayManagementApi service to send a POST request to my API Gateway. The reason I need this service is because of the @connections
value in my POST URL.
POST https://{api-id}.execute-api.us-east-1.amazonaws.com/{stage}/@connections/{connection_id}
These requests use IAM authorization and have to be signed with Signature Version 4. The ApiGatewayManagementApi service handles this signing for us, allowing us to simply pass our connection ID and data to the postToConnection
method.
To learn more about Signature Version 4, click here.
So whenever a connected user sends a message to the room, our React app, uses the test
route to send the data to our API Gateway, which forwards it to the lambda, which then POSTs the message to every connected user, except the one that sent the message.
Now this covers the code our lambda needs to process messages, but in order for the communication between the API Gateway and lambda to work, we need to give our lambda some special permissions. I set this up with a special role I created for the Execution Role of the lambda.
The most important ones are:
AmazonAPIGatewayInvokeFullAccess
AWSLambdaBasicExecutionRole
AWSLambdaVPCAccessExecutionRole
These allow me to make the POST request to the API gateway, use CloudWatch for my logs, and finally access the VPC with my EC2, containing my MongoDB database.
We now look at our EC2 instance, This is a standard t2.micro
, Amazon Linux instance, all within Free Tier. I added a security group to the instance, enabled SSH access for my IP, so that I could install MongoDB in the instance.
I won’t go into too much detail on connecting my lambda and EC2 instance here, because I’ve already done it in this article. In it, I walk you through the process of setting up a VPC for my lambda, my EC2 instance and getting all the VPC components in place to allow communication between the two. I’m using VPC Peering to enable this, since it seemed like a good opportunity to learn how to set it up. In addition my lambda requires internet connectivity for this to work, which I lose once I pick a VPC for it to access. So this further required setting up a NAT Gateway, connected to an Internet Gateway, since I wanted my lambda to remain in a private subnet. All this is covered in the article I linked.
Now we need to complete the routes in our API Gateway, start by creating a new route key. Once it appears in the list below, click on it to set up the Integration. Make sure you keep Lambda Proxy integration selected if you’re following along with this article. Here’s a quick look at how to set it up.
Once that’s done, and you hit save, you’ll see a screen requiring you to grant permission to your API Gateway to invoke the lambda.
After the route is set up, you’ll land on this screen:
We don’t need to set up an Integration response, since we don’t intend to return any data to the user when they access a route in our API Gateway. All our responses go through the POST request our lambda will send separately.
You need to have similar routes set up for your $connect
, $disconnect
and test
routes. For this app I’m using the same lambda for all routes, but you can split this up into multiple lambda functions, depending on the functionality of your app. Also, I’m using the test
route as they main chat route in my lambda, but you can call it anything, as long as you check for the right route key. Once all your routes are set up, the next step is to Deploy the API. You do this by clicking on the Actions button on the dashboard. Choose Deploy API, which will show you this screen.
If you click on the Deployment stage drop down you get the option of New Stage, use that to create a new stage to deploy in. I created Dev for my API.
After the stage is deployed you are taken to this screen, where you are provided both, the WebSocket URL we will use in this part of the article, as well as in the React app, and the Connection URL our lambda uses.
With that it’s time to test our connection. I’m using the Amazon recommended wscat
module to do this.
In the GIF you can see when I connect through two terminals, every time I send a message to my test
route, using my route key rcaction
the message is successfully posted to every other connected user, which in this case is my other terminal.
The wscat
command format is:
wscat -c wss://{api-id}.execute-api.us-east-1.amazonaws.com/{stage}/
Click here for Part 2, where I build the React app to connect to our WebSocket API Gateway. We’ll use a websocket module to set it up, and allow users to focus on sending messages, with the app handling the formatting of data and dispatch to our API Gateway.