Building an Unflashy Serverless COVID-19 Tracker with AWS
Tags: serverlessarchitecture, php, amazonapigateway, bref
Published: 2020-03-28 22:39:00

---
title: Building an Unflashy Serverless COVID-19 Tracker with AWS
published: true
date: 2020-03-28 20:21:12 UTC
tags: serverless-architecture,php,amazon-api-gateway,bref
canonical_url: https://medium.com/@guillermoandrae/building-an-unflashy-serverless-covid-19-tracker-with-aws-afb6f54ffc52
cover_image: https://cdn-images-1.medium.com/max/1024/0*3Va9kPeE7F0CvuBU
---
### Really? Why?

To keep your mind off the severity of the current situation. To stay busy, and to use your skills to keep your family and friends informed. To learn something new while you keep your busy behind at home.

So… a week or so ago, out of nowhere, I decided that I wanted to be able to get the most recent COVID-19 numbers without having to use a browser. There were a few states in particular that were important to me, and I wanted to be able to see the number of confirmed cases in those states by typing a command in a terminal.

![](https://cdn-images-1.medium.com/max/1024/1*xSrksceaNMr7L5hupORmKg.png)<figcaption>Boom, bap, bip.</figcaption>

That’s all there was to it when I started. I figured I could ping [the CDC’s site](https://www.cdc.gov/) to get the info, but I noticed that the department of health websites seemed to have the most up-to-date information. I took a look at a few of those DOH sites, viewed the source (the way only _very_ _cool people_ do when they surf the Interwebs), and determined that I could easily scrape some pages to get the info I wanted using regular expressions, DOM traversal, and super fancy string parsing.

What started out as a CLI became a CLI + web API because I couldn’t help myself. [An HTML page](https://guillermoandraefisher.com/covid.html) came to be because my wife wanted to know what I was working on, and I didn’t think she’d be too impressed by a JSON string. I went serverless because this was a green field project and it’s 2020 and my free time is too precious to be spent OS patching and because why in the world would I pay for a server and because _come on already!_

### What’s Involved

#### PHP… again

I wanted to do this quickly, so I used the language that I knew best: PHP. I threw together a [PHP skeleton](https://github.com/guillermoandrae/php-skeleton) to start the millions of unfinished projects I tend to bring to life, and I used it for this project. I also used [Flysystem’s](https://github.com/php-cache/filesystem-adapter) [PSR-6](https://www.php-fig.org/psr/psr-6/) implementation for filesystem caching — I was actually unaware of the PSR-6 vs. [PSR-16](https://www.php-fig.org/psr/psr-16/) discussions that popped up over the years; I chose PSR-6 because I thought the provided CacheItemPoolInterface would work well in this situation, allowing me to easily change cache storage options if I felt the need.

#### Other Stuff

An older version of [Bref](https://bref.sh/) (I’m not ready to make the jump over to the version that leverages the [Serverless framework](https://serverless.com/)) made it easy for me to use [AWS SAM](https://aws.amazon.com/serverless/sam/)to deploy an [AWS Lambda](https://aws.amazon.com/lambda/) function. I used [Amazon API Gateway](https://aws.amazon.com/api-gateway/) to serve up the endpoint, and used [Amazon CloudWatch](https://aws.amazon.com/cloudwatch/) to schedule the population of the cache, which lives in [Amazon S3](https://aws.amazon.com/s3/).

### Scraping

This was the most interesting challenge because it required me to keep up with the way each state rolled out their changes. New York, for instance, made several changes to the way they displayed their data over time. Every few updates, they’d wrap the count in a different set of tags, or surround it with different characters. In most cases, though, it came down to doing some simple pattern matching:

```php
$page = file_get_contents('https://the.url'); // grab the HTML
preg_match('/total to (.*) confirmed cases/', $page, $matches);
```

I invariably ended up having to do some cleanup on whatever existed in `$matches[1]`, but that ended up being pretty simple. I did some similar matching to get the timestamps for the updates.

### Putting it All Together

#### Deploying with Travis CI and AWS SAM

To get the code deployed up to a Lambda function, I followed the steps I walked though in [an older post about Bref and Lambda](https://medium.com/@guillermoandrae/automate-deployment-of-php-applications-to-aws-lambda-with-bref-aws-sam-and-travis-ci-90231dfae6ff). The one new thing to note is that I had to set the Python version in the .travis.yml file to 3.6.7 or 3.7.1 due to updates in the AWS tools.

#### API Gateway Setup

Once the Lambda function was deployed, I created a REST API endpoint using the API Gateway.

![](https://cdn-images-1.medium.com/max/1024/1*5okgHMDF2cycy_aoQm2zgw.png)<figcaption>Go with REST, unless you don’t want to…</figcaption>

When I clicked on “ **Build** ” in the REST API box, I was led to a screen where I was able to add resources and methods to the API. Using the “ **Actions** ” dropdown, I created a resource named casesby choosing the “ **Create resource** ” option. I enabled [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) for good measure.

![](https://cdn-images-1.medium.com/max/1024/1*lQvtswT18cOiJ-ujOCBs8g.png)<figcaption>Cases? Yeah… that works…</figcaption>

I then chose “ **Create method** ” from the same dropdown to associate the GET HTTP method with that resource. I was able to point to my Lambda function from the screen that appeared. I used [Lambda Proxy Integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html) to proxy incoming requests straight through to the Lambda function.

![](https://cdn-images-1.medium.com/max/1024/1*Fff7yOIIbQm-pb3rYo6EiA.png)<figcaption>HTTP methods make the world go ‘round</figcaption>

Once it was all set up, I saw a no-fuss GET method configuration.

![](https://cdn-images-1.medium.com/max/1024/1*dHuybuD--R4KWLdL_YvkPA.png)<figcaption>Voila!</figcaption>

I deployed the API by choosing “ **Deploy API** ” from the “ **Actions** ” dropdown, chose a stage name, and added additional stage meta data.

![](https://cdn-images-1.medium.com/max/1024/1*SOuFXvwpSZZrVzCsYkKJBw.png)<figcaption>Be practical here</figcaption>

Once I was done deploying the API, I saw a screen that featured a link to it, and was also presented with options for things like caching and client certificates.

![](https://cdn-images-1.medium.com/max/1024/1*BXerE2Q_FMtJ9D_L78L5qw.png)

If you’re following along because you’re building an API of your own, then you’ll probably want to associate it with a customized domain name — if so, follow the steps in the [AWS docs](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-custom-domains.html).

### Use This as a Starting Point…

I really didn’t do much for this project, and I’m sure _you_ can do much, much more with it. If you’re interested, you can save the data to a [DynamoDB](https://aws.amazon.com/dynamodb/) table and use that data to build visualizations. You can ditch the scraping altogether and just use the data provided by [The COVID Tracking Project](https://covidtracking.com/) (thanks for making me aware of it, [Branden](https://brandenbuilds.com/)). You can build a more engaging application using React or Vue. Whatever you do, though, **do it from the safety of your home**.

If you end up [using the code](https://github.com/guillermoandrae/coronavirus-reporter), or find this post useful, please let me know.