A Serverless App with AWS SAM to Rediscover Liked Tweets
Tags: aws, serverless, lambda, sam
Published: 2021-01-23 15:05:16

## Introduction

Following the tradition of  [solving our little Twitter problems with serverless](https://www.lastweekinaws.com/blog/counting-twitter-followers-over-time-the-corey-quinn-way/), this post describes an app that would help you rediscover tweets that you have liked in the past. Of course, with the goal of learning how to do serverless along the way. We will be building this simple yet interesting app using [AWS Lambda](https://aws.amazon.com/lambda/) and related services. For configuring and gluing together all the components we will be making use of [AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html).

On Twitter, you may be following a bunch of smart folks and often tap the like icon for interesting thoughts they share. However, once you have liked a tweet, it's quite hard to rediscover that after a while. Twitter's native option for that is a chronological display of all liked Tweets with an endless scroll. That, let's just say, isn't that cool.

The serverless app we are going to build will show you back a liked tweet of yours with some serendipity added to the mix. Demo page is [here](https://random-likes.harprit.dev/). Though, once [Twitter API rate limit](https://developer.twitter.com/en/docs/twitter-api/v1/rate-limits) is reached, it may not work :-( Below is a snapshot of how it should look though. Also, as we shall see, you may follow along the steps and deploy the same for yourself from the source [here on Github](https://github.com/harprit/random-likes).

![Random Twitter Likes Demo](https://www.harprit.dev/serverless-app-random-twitter-likes/Demo.jpg)

<br>

## Prerequisites

Before starting out building this app, here's a list of things you need to have. You may find further details in the [readme](https://github.com/harprit/random-likes/blob/master/README.md) as well.

- **AWS SAM CLI Installed**

  Check the introduction from [these docs](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html).

- **Twitter API Keys and Tokens** 

  Get yours using a [Twitter Developer](https://developer.twitter.com) account. We shall be storing them in AWS SSM Parameter Store.

- **Java 8 with Maven**

  Lambda function is written in Java. You may skip this if you prefer to build a similar Lambda function in a different programming language.

## Components

Here's a view of different components of the app and how they interact:

![Components](https://www.harprit.dev/serverless-app-random-twitter-likes/Flow.jpg)

1. The flow gets triggered when a user, via the browser, invokes the API Gateway endpoint URL passing along their Twitter username. 
2. API Gateway, in turn in an event driven fashion, invokes the Lambda function with a request object containing the username.
3. Lambda function on invocation fetches Twitter Authentication keys from AWS SSM Parameter Store. Followed by using these keys to invoke Twitter API to fetch a batch of tweets liked by the user. Finally, one random tweet out of this list is returned as response and eventually gets shown back to the user.

In the upcoming sections of this post, let's look at each of these components one by one.

### Parameter Store

For invoking the Twitter APIs, we need to first obtain authentication keys using a [Twitter Developer account](https://developer.twitter.com). Once we have the keys, to be able to securely store and access them from Lambda function, we will make use of [AWS SSM Parameter Store](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html). To keep things simple and fetch all authentication related details in a single call to SSM, we shall be storing them as a comma separated StringList param as follows:

<br>

![Parameter Store](https://www.harprit.dev/serverless-app-random-twitter-likes/ParameterStore.png)

<br>

Next, we need to define this [parameter](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html) in the SAM template. This, as we shall see later in the SAM config, will be referred to as an environment variable by the Lambda function. This practice helps in achieving customization of stacks during [deployment using SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html). We may [pass along different parameter names](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html) at the time of deployment, that correspond to respective values for different environments.

For example, let's say that, here we have defined a parameter resource TwitterAuthParam with default value as TWITTER_AUTH for all non prod environments. When we need to run this app on prod environment, we may override the same during deployment with value TWITTER_AUTH_PROD. Accordingly, the Lambda function with no change in code will be able to access the respective SSM param depending on the environment that it's running in.

<br>

```yaml
Parameters:
  TwitterAuthParam:
    Type: String
    Default: TWITTER_AUTH
```

<br>

### Lambda

AWS Lambda function, defined for this app, contains the [code](https://github.com/harprit/random-likes/blob/master/functions/RandomLikesFunction/src/main/java/com/hs/randomlikes/App.java) that for a given username invokes Twitter APIs to get a list of liked tweets for the user. This is followed by simple randomization to choose one liked tweet and return it back as a response. Lambda function is written in Java and makes use of the awesome [Twitter4J library](http://twitter4j.org). Of course, the same can be achieved in any language using a similar Twitter library.

[Lambda function code](https://github.com/harprit/random-likes/blob/master/functions/RandomLikesFunction/src/main/java/com/hs/randomlikes/App.java) follows the best practice of performing initializations in the constructor code like fetching authentication keys from Parameter Store and creating Twitter connection instance. Keeping these initializations outside the handler function means that they get carried out only once during initial Cold Start for a given instance. This helps in making faster all subsequent handler function invocations on the same object.

Here's the part of the SAM Template defining config for the Lambda function:



<br>

```yaml
RandomLikesFunction:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: RandomLikesFunction
    Handler: com.hs.randomlikes.App::handleRequest
    Runtime: java8
    MemorySize: 512
    Timeout: 15
    Environment:
      Variables:
        TWITTER_AUTH_PARAM: !Ref TwitterAuthParam
    Policies:
      - SSMParameterReadPolicy:
          ParameterName: !Ref TwitterAuthParam
    Events:
      GetResource:
        Type: HttpApi
        Properties:
          Method: get
          Path: /u/{name}
          ApiId: !Ref RandomLikesHttpApi
```

<br>

### API Gateway

[Frontend](https://github.com/harprit/random-likes/tree/master/public) for the app uses vanilla JS which invokes the API Gateway URL with user's Twitter username as a parameter. It's using [HTTP flavor of API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) which is faster and simpler to use compared to [REST](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) (pun intended :-) 

Further, as we are using [HTTP proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-api-integration-types.html), API Gateway converts the HTTP request into a JSON and passes this to the Lambda function. In Java world, this translates into an event of type `APIGatewayProxyRequestEvent` as an input to the Lambda function. On the way back Lambda function returns `APIGatewayProxyResponseEvent` as output, which eventually gets converted by API Gateway into an HTTP response eventually returned back to the caller.

API Gateway, other than providing an interface to the Lambda function, also helps secure the app. This is required as the API endpoint is not authenticated and we need to define [throttling](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-throttling.html) limits to avoid any abuse. Or, in other words, to avoid ending up with a big AWS bill due to numerous Lambda invocations.

Finally, we need to enable CORS config to ensure that the browser allows our domain to invoke API Gateway endpoint and is able to access the response.

<br>

```yaml
  RandomLikesHttpApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      DefaultRouteSettings:
        ThrottlingBurstLimit: 5
        ThrottlingRateLimit: 20
      CorsConfiguration:
        AllowMethods:
          - GET
        AllowOrigins:
          - "https://random-likes.harprit.dev"
```

<br>

## SAM Template

Finally, let's have a look at the complete AWS SAM configuration template for the app. Here's a visual of the same that describes different sections of the template and shows how SAM glues together all the components discussed above. 

**Check [here](https://www.harprit.dev/serverless-app-random-twitter-likes/#sam-template) for a better resolution version of this image.**

<br>

<img src="https://www.harprit.dev/serverless-app-random-twitter-likes/Sam.jpg" alt="AWS SAM" width="500"/>

<br>

## Conclusion

You may may find further details like how to build and deploy the app from the [source code and README](https://github.com/harprit/random-likes). As mentioned above, purpose behind this app was to build something fun and a bit useful while learning to build serverless apps with AWS SAM along the way. I hope it met that goal somewhat.

<br>