Part two: serverless real-time ReactJS app — AWS IoT MQTT

Let’s learn how to leverage the Serverless Framework to automate an AWS backend🔥

Serverless Guru
Jun 17 · 8 min read

This article is part of a series:

GitHub Repository:

In , we covered how to hook up AWS IoT MQTT with a ReactJS frontend. Giving our ReactJS application real-time communication where every device will instantly* be updated when a new message is published to our MQTT topic.

In part two, we will be extending the application to include the Serverless Framework and do the following:

  • Create a basic Serverless Framework project
  • Dive into best practices with Serverless Framework
  • Add AWS resources
  • Deploy to AWS
  • Test the deployed AWS backend

Let’s get into it!

Create a basic Serverless Framework project

If you’re not familiar with the Serverless Framework and/or do not have an AWS account set up already. Please go through our article or optionally visit our and take the course.

Create a new project

We will create a new project using a Serverless Framework template which automatically sets up a NodeJS Lambda function ready to deploy to AWS. Since we already created a project last time, just make sure you’re in the project directory.

sls create -t aws-nodejs -p backend -n real-time-weather

This command will create a new Serverless project using aws-nodejs template giving us some boilerplate in a folder called backend with a service name equal to real-time-weather 🔥 🔥

First off let’s crack open our favorite text editor and then I’ll show you how to copy/paste like a Senior Cloud Developer!

$real-time-weather: code .

Update the provider section (add command line flags)

We will want to be able to pass in a dynamic stage variable, a dynamic region variable, and a dynamic profile variable (for ).

These variables will help ensure:

  • the AWS resource names can be different (multi-stage deployments)
  • the region can be different (multi-region deployments)
  • the AWS account can be different (multi-account deployments)

Let’s copy the following code into our serverless.yml file:

provider:
name: aws
runtime: nodejs8.10
stage: ${opt:stage, "dev"}
region: ${opt:region, "us-west-2"}
profile: ${opt:profile, "default"}

This will add the following things:

  • a provider of aws
  • a runtime of nodejs8.10
  • an optional stage flag with a default stage of dev
  • an optional region flag with a default region of us-west-2
  • an option profile flag with a default profile of default

To use these flags we will run the following command:

serverless deploy --stage <> --region <> --profile <> -v

With the resources/provider.yml isolated, let’s now update the serverless.yml to reference our separate file.

provider: ${file(resources/provider.yml)}

Now when we go to make the deployment the provider property will be populated by the resources/provider.yml file.

Create custom variables

Let’s create a custom.yml file under our resources folder at resources/custom.yml. This file will hold all of our custom variables which we can centralize in one place instead of hard-coding in multiple places.

tableName: ${self:service}-${self:provider.stage}-data

The key things to pay attention too:

  • ${self:service} is equal to real-time-weather
  • ${self:provider.stage} is equal to dev or the result of --stage <>

With the resources/custom.yml isolated, let’s now update the serverless.yml to reference our separate file.

custom: ${file(resources/custom.yml)}

Now when we go to make a deployment the custom property will be populated by the resources/custom.yml file as if it was written inline.

Add AWS resources

Now that we have a serverless project. Let’s start sprinkling ✨ in our AWS resources. We will add the following:

Add DynamoDB

Now let’s create a general.yml file at resources/general.yml and copy the following:

WeatherTable:
Type: AWS::DynamoDB::Table
Properties:
KeySchema:
- AttributeName: zip
KeyType: HASH
AttributeDefinitions:
- AttributeName: zip
AttributeType: S
BillingMode: PAY_PER_REQUEST
TableName: ${self:custom.tableName}

This will create a DynamoDB table which has the following settings:

  • a primary key of zip which will be a string
  • a billing mode set to pay per request (e.g. no traffic = no cost)

Let’s update the serverless.yml file to include a reference to our resources/general.yml file.

resources:
Resources: ${file(resources/general.yml)}

Add the API Lambda function

Now let’s create a functions.yml file at resources/functions.yml and copy the following:

api:
handler: api.handler
events:
- http:
path: /api/weather
method: GET
cors: true
environment:
TABLE_NAME:
Ref: WeatherTable

Here we are saying a few things:

  • connect our Lambda to the handler() function inside the api.js file
  • create an API at /api/weather which can handle GET requests
  • set cors: true to allow two-way API communication
  • pass an environment variable called TABLE_NAME to our Lambda function
  • reference the DynamoDB table name dynamically via Ref: WeatherTable

We then need to update the serverless.yml to utilize our resources/functions.yml file.

functions: ${file(resources/functions.yml)}

Add AWS IAM permissions

Our Lambda function needs to have the proper AWS IAM permissions to access DynamoDB and retrieve weather data.

Let’s add a section to help with this under the provider property in our serverless.yml file.

provider:
...
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:GetItem
Resource:
Fn::GetAtt:
- WeatherTable
- Arn

This will do the following:

  • give our Lambda function access to do dynamodb:GetItem
  • specify we can only dynamodb:GetItem on the WeatherTable

Review progress

Perfect, let’s review the serverless.yml file so far.

service: real-time-weatherprovider:
name: aws
runtime: nodejs8.10
stage: ${opt:stage, "dev"}
region: ${opt:region, "us-west-2"}
profile: ${opt:profile, "default"}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:GetItem
Resource:
Fn::GetAtt:
- WeatherTable
- Arn
custom: ${file(resources/custom.yml)}
functions: ${file(resources/functions.yml)}
resources:
Resources: ${file(resources/general.yml)}

Above, we’ve dramatically reduced the total lines in our serverless.yml file and have chosen to isolate different components of our Serverless backend to separate files. This level of abstraction is particularly useful when you start scaling your projects up.

When your projects grow you may have 3+ DynamoDB tables, a whole slew of supporting AWS services, and 10+ Lambda functions which all need custom variables. As you can imagine this can become hard to manage if it was all pushed into the serverless.yml file.

Therefore we break it up under the resources folder and keep everything nice and neat. That’s called a solid foundation and it’s worth the extra time to set up for every Serverless project you build.

Customize the API Lambda function

First, let’s change the handler.js file to api.js. Then we can paste in the following code:

'use strict';const AWS = require('aws-sdk');const api = {};const tableName = process.env.TABLE_NAME;api.handler = async (event, context) => {
console.log('event', event);
let response = {};
try {
let data = {};
let path = event.path;
if(path.includes('weather')) {
data = await api.handleWeather(event);
}
response.statusCode = 200;
response.body = JSON.stringify(data);
} catch (error) {
response.statusCode = 500;
response.body = JSON.stringify(error);
}
response.headers = { 'Access-Control-Allow-Origin': '*' };
console.log('response: ', response);
return response;
};
api.handleWeather = async (event) => {
if (event.httpMethod === "GET") {
return await api.getCurrentWeatherData({ zip: event.queryStringParameters.zipCode });
} else {
throw new Error(`event method not known ${event.httpMethod}`);
}
};
api.getCurrentWeatherData = (key) => {
return new Promise((resolve, reject) => {
let documentClient = new AWS.DynamoDB.DocumentClient();
let params = {
TableName: tableName,
Key: key
};

documentClient.get(params, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
};
module.exports = api;

This code will do a few things:

  • import aws-sdk allowing us to connect to DynamoDB
  • reference an environment variable via process.env.TABLE_NAME
  • add a handleWeather() function to manage all API request to /weather
  • add a getCurrentWeatherData() function to request the weather by zip

From here we should have all we need to fetch data via zip (e.g. 97205) and get a response from the DynamoDB weather table with the current weather in our zip.

Deploy the backend to AWS

With all of our automation created, thanks to the power of the Serverless Framework. Let’s try deploying our Serverless stack to AWS.

sls --stage test --region us-west-2 --profile default -v

This will deploy our backend to thetest stage and the us-west-2 region with an AWS named profile of default.

Load some data into DynamoDB

Currently, we only have a GET API endpoint so we will manually add data into DynamoDB then try to pull that data out.

Test the AWS backend

Once we have successfully deployed the backend, we can test the API via Postman by making a request with a queryStringParmeter of ?zipCode=97205.

Awesome, we’ve got our data coming back from DynamoDB and we have confirmed that our automation fully works to spin up our entire backend.

Tear down

If you would like to stop here, we can tear down the Serverless backend by running the following command.

sls remove --stage test --region us-west-2 --profile default -v

This will do the following:

  • delete our Lambda function
  • delete our API Gateway endpoint at /api/weather
  • delete our DynamoDB table
  • delete our AWS IAM policies

Next time, we go even deeper

If you enjoyed this tutorial, please stay tuned for part three which will go into creating a Lambda function which is triggered on a schedule and will auto-post messages out to our AWS MQTT devices.

Allowing our users to get real-time weather updates automatically!

Additional Content:

What does Serverless Guru do?

At , we work with companies who want to accelerate their move to Serverless/Cloud Native event-driven development rapidly. We help clients with cloud development, backend development, frontend development, automation, best practices, and training to elevate entire teams. We are engineers first.

What did we miss?

When you leave your answer make sure to either comment below or tweet your answer to on Twitter.

Ryan Jones

Founder & CEO —

LinkedIn —

Twitter —

Thanks for reading 😃

If you would like to learn more about , please follow us on , , , , or !

Serverless Guru

Written by

Exploring the unknown and helping elevate the entire community through written word. https://www.serverlessguru.com