Skip to content

Instantly share code, notes, and snippets.

@joeduffy
Last active January 10, 2022 22:31
Show Gist options
  • Save joeduffy/15a8fa0c7bf3d4ef004571bce7965815 to your computer and use it in GitHub Desktop.
Save joeduffy/15a8fa0c7bf3d4ef004571bce7965815 to your computer and use it in GitHub Desktop.
Website hit counter using AWS DynamoDB, API Gateway, and Lambda

Website Hit Counter Using AWS

This code provisions an AWS API Gateway- and Lambda-based website, and uses DynamoDB to store the hit counter. All infrastructure is "serverless" and pay per usage so there aren't any persistent servers. The project is defined in TypeScript infrastructure as code so that it's easy to spin up and tear down environments without manual steps.

Here is a live instance: https://nltg0oruy1.execute-api.us-west-2.amazonaws.com/stage/

To get it up and running:

  • Clone the repo and cd into it
  • Install Pulumi
  • Run pulumi stack init to create a new stack
  • Run pulumi config set aws:region us-west-2 to initialize the region (feel free to choose an alternate)
  • Run pulumi up
  • Now the website is running, visit it by going to the URL printed by pulumi stack output url
import * as aws from "@pulumi/aws";
import * as AWS from "aws-sdk";
import * as awsx from "@pulumi/awsx";
const hits = new aws.dynamodb.Table("hits", {
attributes: [{ name: "Site", type: "S" }],
hashKey: "Site",
billingMode: "PAY_PER_REQUEST",
});
const site = new awsx.apigateway.API("site", {
routes: [{
path: "/",
method: "GET",
eventHandler: async () => {
const dc = new AWS.DynamoDB.DocumentClient();
const result = await dc.update({
TableName: hits.name.get(),
Key: { "Site": "ACMECorp" },
UpdateExpression: "SET Hits = if_not_exists(Hits, :zero) + :incr",
ExpressionAttributeValues: { ":zero": 0, ":incr": 1 },
ReturnValues:"UPDATED_NEW",
}).promise();
return {
statusCode: 200,
headers: { "Content-Type": "text/html" },
body: `<h1>Welcome to ACMECorp!</h1><p>${result.Attributes!.Hits} hits.</p>`,
};
},
}],
});
export const url = site.url;
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}
name: page-counter
runtime: nodejs
description: A page counter
{
"compilerOptions": {
"strict": true,
"outDir": "bin",
"target": "es2016",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"experimentalDecorators": true,
"pretty": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.ts"
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment