Building a weather monitor with AWS SAM and Terraform — part 3

Richard Chou
4 min readNov 26, 2023

--

Source code can be found here

Part 1 Part 2

This article is the “visualiser” part of the weather monitor. It is comprised of a Lambda function and S3 bucket. The Lambda function pulls weather data from DynamoDB, generates index.html and pushes it up to S3. S3 is configured as static website.

I used Terraform to provision S3 static website because Terraform has good documentation about S3. I also referred to this article.

Of course I can front the S3 with CloudFront to make it support SSL/TLS, but I’ll skip that for now.

Sample index.html

I created a sample html to help me build the UI. The Lambda replaces the x-axis with datetime, and y-axis with temperature. The generated index.html looks like this:

S3 Configurations

The bucket policy was obtained from this article on AWS

Converting it to Terraform:

.deploy/terraform/static-site/iam.tf

data “aws_iam_policy_document” “website_policy” {
statement {
actions = [
“s3:GetObject”
]

principals {
identifiers = [“*”]
type = “AWS”
}

resources = [
“arn:aws:s3:::${var.bucket_name}/*”
]
}
}

The bucket also needs to enable all public access:

And this part does exactly that:

.deploy/terraform/static-site/s3.tf

resource "aws_s3_bucket_public_access_block" "public_access_block" {
bucket = aws_s3_bucket.website_bucket.id

block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}

Final Configured Bucket

The final configured bucket looks like this:

Lambda querying DynamoDB

The primary key of the table is a composite key: “city” is the partition key, and “datetime” is the sort key.

When Lambda queries the table, it has to speficy the partition key:

src/app.ts

var params: AWS.DynamoDB.DocumentClient.QueryInput = {
TableName: 'newweatherdata',
KeyConditionExpression: 'city = :partitionKeyValue',
ExpressionAttributeValues: { ':partitionKeyValue': 'Sydney' },
ScanIndexForward: false,
Limit: 10
}

By default, when we query DynamoDB, the data will be retrieved in ascending order of sort key (datetime). That’s not what we want; we want to get the latest datetime (decending order). To achieve that, we use ScanIndexForward: false.

This is how the queried data looks like in decending order:

{
Items: [
{
city: 'Sydney',
humidity: 61,
datetime: 1692519781720, //20 August 2023 08:23:01.720
temperature: 16.34
},
{
city: 'Sydney',
humidity: 61,
datetime: 1692519721614, //20 August 2023 08:22:01.614
temperature: 16.42
},
{
city: 'Sydney',
humidity: 61,
datetime: 1692519662220, //20 August 2023 08:21:02.220
temperature: 16.42
}
],
Count: 3,
ScannedCount: 3
}

But to get the data to display in index.html, we have to loop from the lowest datetime to highest datetime:

That’s why I used reverse() to reverse the array again when generating data and generating labels:

const generateData = (items: Weather[]): string => {
const reversedItems = items.slice().reverse();
var data = `data: [`
reversedItems.forEach((item: Weather, index) => {
data += item.temperature;
if (index < items.length - 1) {
data += `, `;
}
})
data += `],`
return data
}

You’re a good Rails developer. But have you ever built a Rails stack from scratch?

I’ve written a guide to help people do exactly that, with the help of Docker and AWS Copilot. You will learn how to build a CI/CD pipeline on Github Actions, deploying to multiple AWS accounts, DB migration, and more.

Build a full web stack with Docker and AWS Copilot (Rails Edition) (build-with-aws-copilot.github.io)

--

--

Richard Chou

I write about Ruby, Rails, AWS and JavaScript. Occasionally other things.