Welcome to the second part of building a serverless website on AWS. A link to Part 1 can be found here.
Pre-requisites for Part 2:
Note: A couple of great resources to getting started with SAM are AWS's own Hello World tutorial and, an even better version is written by Chris Nagy.
The second part of the project is the Visitor Count function that counts the number of visitors to the website. It requires storing the visitor count in a DynamoDB database and updating the value everytime the website is viewed. This has to be done automatically, but, the current website is static and based on S3 bucket which only stores files and can't do any computing. Hence classic methods of using a POST method etc won't work which nesseciates the addition of serverless components to the design.
The key features of the Visitor Count Function are:
The whole process of creating the API, DynamoDB table, Lambda function, permissions for Lambda to access DynamoDB etc are done using AWS Serverless Application Model (SAM). SAM uses Infrastructure as Code (IaC) concept where the configuration is written in a template file which gets deployed in AWS using SAM Command Line Interface (SAM CLI).
The Python code for the Lambda function to update the visitor count and fetch the updated value from DynamoDB is shown below. The following code is saved as a .py file and placed in a folder named
visitor_count. The directory structure can be seen in the github directory.
import json import boto3 import os # Initialize dynamodb boto3 object dynamodb = boto3.resource('dynamodb') # Set dynamodb table name variable from env #ddbTableName = os.environ['databaseName'] ddbTableName = 'VisitorCount' table = dynamodb.Table(ddbTableName) def lambda_handler(event, context): # Update item in table or add if doesn't exist ddbResponse = table.update_item( Key={ 'id': 'count' }, UpdateExpression='SET visitor_count = visitor_count + :value', ExpressionAttributeValues={ ':value':1 }, ReturnValues="UPDATED_NEW" ) # Format dynamodb response into variable responseBody = json.dumps({"count": int(ddbResponse["Attributes"]["visitor_count"])}) # Create api response object apiResponse = { "isBase64Encoded": False, "statusCode": 200, 'headers': { 'Access-Control-Allow-Headers': 'Content-Type', 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'OPTIONS,POST,GET' }, "body": responseBody } # Return api response object return apiResponse
The next step is to write the SAM template to deploy the resources.
SAM uses YAML (YAML Ain't Markup Languageā¢) language for the template and deploys it in AWS using SAM Command Line Interface (CLI). While it simply uses CloudFormation to deploy resources, whereas traditional CloudFormation requires the template to be manually uploaded to an S3 bucket and using console to deploy the template, SAM sets up an S3 bucket, keeps it in sync with the template file and other supporting files such as Lambda python code file etc and deploys the template, all in one go.
The following image shows the template used to deploy the rescources required for visitor count feature on the website.
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > Visitor Count Feature SAM Template for oncloud9.net Globals: Api: Cors: AllowMethods: "'GET,POST,OPTIONS'" AllowHeaders: "'content-type'" AllowOrigin: "'*'" AllowCredentials: "'*'" Function: Timeout: 3 Resources: VisitorCountFunction: Type: AWS::Serverless::Function Properties: CodeUri: visitor_count/ Handler: app.lambda_handler Runtime: python3.8 Policies: DynamoDBCrudPolicy Events: VisitorCount: Type: Api Properties: Path: /visitor_count Method: get VisitorCount: Type: AWS::Serverless::SimpleTable Properties: PrimaryKey: Name: myKeyName Type: String Outputs: VisitorCountApi: Description: "API Gateway endpoint URL for Prod stage for Visitor Count function" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/visitor_count/" VisitorCountFunction: Description: "Visitor Count Lambda Function ARN" Value: !GetAtt VisitorCountFunction.Arn VisitorCountFunctionIamRole: Description: "Implicit IAM Role created for Visitor Count function" Value: !GetAtt VisitorCountFunctionRole.Arn
The Python code to call the REST API can be seen below. The following code is saved in a script file called api_visitorcount and placed along in the scripts folder of website files.
Whenever the website is opened in the browser or refreshed, the function gets triggered and calls the Rest API
// GET API REQUEST async function get_visitors() { // call post api request function //await post_visitor(); try { let response = await fetch('https://xxxxxxxxxx.execute-api.eu-north-1.amazonaws.com/Prod/visitor_count', { method: 'GET', headers: {} }); let data = await response.json() document.getElementById("visitors").innerHTML = "Total visitors: " + data['count']; console.log(data); return data; } catch (err) { console.error(err); } } get_visitors();
The last step is to find a location for the Visitor Count to show on the website. Just add the following line to the index.html file at the location where you want the visitor count to show.
<p id="visitors"> &nbsp; </p>
The visitor count function should be working now. The count updates everytime the page is visited. It updates the count even for a page refresh instead of updating the count for a new visitor.
To keep track of the cookies while updating the visitor count may be possible but leaving it for a future project.
The next step is to automate the whole process using a Continuous Integration / Continuous Deployment (CI/CD). Whenever the code for the website is changed, it has to be manually pushed into the S3 bucket and the current Cloudfront distribution has to be invalidated to show the updated website. A CI/CD deployment makes use of repositories such as Github to fastrack the process of deployment to automatically upload the code to the S3 bucket, invalidate the Cloudfront cache to show the updated website which is done in Part - 3.