Skip to content

Instantly share code, notes, and snippets.

@joeduffy
Last active October 20, 2019 22:35
Show Gist options
  • Save joeduffy/289fd9b4c381a96c4c5b7a412984e8cc to your computer and use it in GitHub Desktop.
Save joeduffy/289fd9b4c381a96c4c5b7a412984e8cc to your computer and use it in GitHub Desktop.
A serverless website with dynamic hit counter on Azure

Website Hit Counter Using Azure

This code provisions an Azure AppService website powered by Azure Functions, and uses Azure Storage 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://site7bb58a26.azurewebsites.net/api/site

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 azure:location WestUS 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 azure from "@pulumi/azure";
import * as azureStorage from "azure-storage";
const rg = new azure.core.ResourceGroup("page-counter-rg");
const acct = new azure.storage.Account("hitsStorage", {
resourceGroupName: rg.name,
accountReplicationType: "LRS",
accountTier: "Standard",
});
const hits = new azure.storage.Table("hits", {
storageAccountName: acct.name,
});
const site = new azure.appservice.HttpEventSubscription("site", {
resourceGroup: rg,
callback: async () => {
const svc = azureStorage.createTableService(acct.primaryConnectionString.get());
return await new Promise<azure.appservice.ExtendedHttpResponse>((resolve, reject) => {
const key = "ACMECorp";
svc.retrieveEntity(hits.name.get(), key, key, (err, result: any, resp) => {
const count = (err || !result || !result.Hits) ? 1 : (result.Hits._ + 1);
const record = { PartitionKey: key, RowKey: key, Hits: count };
svc.insertOrReplaceEntity(hits.name.get(), record, (err, result, resp) => {
if (err) {
reject(err);
} else {
resolve({
status: 200,
headers: { "Content-Type": "text/html" },
body: `<h1>Welcome to ACMECorp!</h1><p>${count} hits.</p>`,
});
}
});
});
});
}
});
export const url = site.url;
{
"name": "page-counter-az",
"devDependencies": {
"@types/node": "^8.0.0"
},
"dependencies": {
"@pulumi/azure": "^1.0.0",
"@pulumi/pulumi": "^1.3.4",
"azure-storage": "^2.10.3"
}
}
name: page-counter-az
runtime: nodejs
description: A page counter on Azure
{
"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