Serverless Websites with Azure Storage
The final component of the basic serverless architecture is a client frontend. The most common one is web-based, but obviously mobile apps or chatbots are also used. We will be using a web-based frontend in this book.
This is the area that serverless is least developed in. The core idea, however, is to have some way of serving the raw HTML, CSS, and JS files to the client, and having JavaScript call the serverless backend from the client side. The only issue is where and how to host those HTML files.
There are multiple approaches to this. Many applications will be expanding on a current platform, perhaps a CMS such as Adobe Experience Manager, Wordpress, or Sitecore CMS. In that case, the best approach is likely to be embedding your frontend application as a JavaScript Single Page Application in a framework such as Angular, React, or Vue.
If working with a completely greenfield application where you can build a truly serverless application architecture, however, there are a greater range of options, which are listed in order of "serverless-ness" in the following list. One thing to be clear about, however, is that this isn't a fully solved problem yet, so each option will have some disadvantages that will hopefully be fixed over time:
Use Azure Storage Static Website Hosting: This has very recently come out of preview, and is now in General Availability. It may be a little unstable given how new the feature is. This feature allows you to use Azure Storage as the repository for your HTML, CSS, and JS files, and set a default home page and a default error page. This is the most serverless option as there are no implementation details or management requirements whatsoever—you can literally put your frontend code there and it will work. This will be the option we will follow in this book.
Serve files using an Azure Function: The second option is to use an Azure function as a way of hosting your HTML, CSS, or JS files. If you put your HTML code files into the C# project, you can reply to any HTTP Trigger with them. Azure Functions have standard IIS routing too, which can be used to route to the different pages. This has a few disadvantages though: Azure Functions are charged per gigabyte of RAM per second, so using them to simply transfer potentially large files isn't ideal (as that file will be loaded into the RAM and will need to be encoded and decoded, resulting in needless expense). Also, it isn't very serverless—this solution requires custom, non-business-value-oriented code just to serve up web pages. It will, however, scale along with your serverless backend, and Azure Functions are more mature than Azure Storage Static Website Hosting.
Deploy Files to an Azure App Service: The third option is to deploy the files to an Azure App Service. This Platform-as-a-Service technology is well-suited to this task and can host more complex MVC applications as well. They can also scale horizontally without management, which is advantageous. However, the number is limited depending on how "big" a resource you demand. The major issue is the level of management and implementation detail. All we want as serverless developers is a bucket to put files in, and Azure's App Services have all sorts of details we aren't interested in, for example, the number of virtual cores on each instance or the RAM currently being used. One point in their favor for small projects though is the "free" category of hosting, which allows you to deploy a custom web app to the internet for free. This is really useful for prototypes, too.
As you can see, the options progress in management level, adding more and more effort expended into non-business value activities. This is without going down to the level of maintaining a virtual machine or a Kubernetes cluster or, even worse, a physical machine in a rack.
Note
There is in fact a fourth option for utilizing AWS. The storage service in AWS is called S3 and has a fully supported static website hosting feature, which you can read about here: https://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html. There aren't any compatibility issues in doing this—latency isn't even an issue as users will be calling the Azure Functions' endpoints from their browsers. Lambda (AWS's FaaS service) can also serve files like Azure Functions.
Exercise 3: Hosting a Serverless Website on Azure Storage
The aim of this exercise is to host a simple Hello World! page on Azure Storage, thus creating a serverless website:
First, you need files to host. Create a new folder called ServerlessWebsite in your development area and create new index.html and error.html files in there:
Create a simple page for both the index and error. The following code can be used to display Hello World!:
<html> <body> <h1>Hello World!</h1> </body> </html>
The following example can be used to display Error:
Now that you've created the files to host, let's create an account on Azure Storage where you can host these files. Open the Azure Portal and search for "storage":
Select Storage accounts. You should see a screen like the one shown in the following screenshot, with at least one storage account already existing from when you created it. You may see more if you have already been using Azure and have already made some storage accounts:
Note
It's possible that you will see other categories of storage account in the search, such as classic. This depends on your Azure Account, so you won't necessarily see any others. Just always select the one that precisely says Storage accounts.
Click Add. You should be taken to a screen like the one shown in the following screenshot. Enter the same resource group that you did for Cosmos DB and the Azure Function App. Give your storage account an appropriate name for hosting a website (the one in this exercise is named webfrontendadvanceds). Choose a location near to you. Choose Standard performance, StorageV2, and simple Locally Redundant Storage or RA-GRS. In production, you will probably want a greater level of georedundancy, but for development purposes, locally redundant storage is fine. Premium performance is actually designed for virtual machine disks, not for web hosting, and is not currently supported with static site hosting. Finally, choose the Hot access tier; Cold is for archiving data:
Note
Note that storage account names have to be globally unique. Hence, do not try and create a storage account with the exact same name as the one that's used in this exercise.
Click the Review + create button and then create the storage account. This may take a little while to provision. You will receive a notification in the top right of the page when it has been created:
Now, you can host the files you created on your Azure Storage account. Go to the storage account you just created (it will appear on the pane on the left) and click on it:
Scroll down the left column and look for Static website (preview) under Settings. Enable it and set your Index document name to index.html and your Error document path to error.html, as shown in the following screenshot:
Click on the Azure tab. We are going to use the Azure Storage extension of Visual Studio Code to deploy the site. Click the Azure Storage account and open the Blob Containers sub-item until you get to the $web item. Right-click it and select Deploy to Static Website:
Note
Using the Azure Storage extension of Visual Studio Code is the easiest manual way to upload files to Azure Storage. If you are doing an automated deployment, you should use a PowerShell script to connect.
You will now be prompted to select a folder to deploy. Select the ServerlessWebsite folder:
Click Delete and Deploy if the following message comes up. It won't if you have never deployed anything to this Azure Storage account before:
As before, updates on the deployment will appear in the bottom right:
You can finally test your website by visiting the address of your Azure Blob Storage account in your browser. This can be found by clicking on the browse site button in the successful deployment update, going back to the static website part of the portal, or by using the pattern they all follow: https://{StorageAccountName}.z6.web.core.windows.net:
You now have the fundamental building blocks of a complete serverless architecture. With Azure Storage hosting your website, you can build any web application. Of course, other client-side delivery methods such as mobile apps and chatbots are possible. Mobile apps are a perfect fit, simply calling the serverless backend directly. Chatbots would use Alexa or Google Assistant as their frontend and call in to the serverless backend.
Exercise 4: Displaying Product Data on Your Serverless Website
In this exercise, you will be displaying product data from the serverless database (Cosmos DB) on the serverless website hosted in the Azure Storage, using the Azure Function you created in the first exercise. It will serve as the basis for the rest of your work on this book, and an inspiration for how easy and quick this development process is:
First of all, you need some product data to display. Go to the Cosmos DB instance in the Azure Portal and select Data Explorer:
Click on the New Database option to create a database. Call it serverless and click on OK:
Next, click on the New Collection option and create a collection called products in the serverless database that we created in the preceding step. Set its throughput to 400 Resource Units (this is how Cosmos DB charges for compute usage on queries) and set the Partition key to /colour. Again, click on OK:
Click on Documents inside the Collection, as shown in the following screenshot:
Click on the New Document option. Insert some records into the Cosmos DB, which will represent products. An example is shown in the following code. Make sure that you do not remove or misspell any of the property names as it will cause problems later. The ID follows the pattern {typeId}_{name}_{colour}_{size}. Cosmos DB will add some fields, including a very useful Etag, which will be used later. In general, you don't need to worry about the automatically added fields; Cosmos DB will handle them:
{ "id":"tshirt_metallica_black_xl", "typeId": "tshirt", "name": "Metallica", "colour": "black", "size": "xl", "quantityInStock": 100 }
You have now added your product data to Cosmos DB. Now, you need to display this data on your website using the Azure function we created previously.
Open the GetProducts Azure Function from Exercise 1, Creating an Azure Function. Install the DocumentDB package by opening the terminal in the ProductsApi folder and entering the following command:
dotnet add package Microsoft.Azure.WebJobs.Extensions.CosmosDB --version 3.0.1
Note
Due to version upgrades, the latest version may not exactly match the one given here. We recommend that you use the latest version for this and the subsequent exercises and activities, in order to ensure that the command functions.
Next, we need to ensure that all the NuGet packages are installed, which we do by "restoring". Use the following command:
dotnet restore
Your file will look as follows:
We need to give our function the ability to read from the Cosmos DB. This is done using a DocumentClient object from the SDK. Add a using statement for Microsoft.Azure.Documents.Client and create a private static DocumentClient property called client:
private static DocumentClient client = new DocumentClient(new Uri("cosmos endpoint"),"key");
Next, we need the Unique Resource Identifier (URI) for the Collection inside the Cosmos DB instance. This will allow us to create queries against that collection. Create a private static Uri property that uses the UriFactory to create a URI for the DocumentCollection:
private static Uri productCollectionUri = UriFactory.CreateDocumentCollectionUri("serverless","products");
Your file will look as follows:
Add a private static QueryOptions property with the MaxItemCount set to –1. This sets the number of docs returned in a query to infinite, which is something to be cautious of as your database scales:
private static readonly FeedOptions productQueryOptions = new FeedOptions { MaxItemCount = -1 };
Now, we need to return the products from Cosmos DB. We don't need the post method anymore, so delete post from the function signature. Change the return type to Task<List<Product>>. The Cosmos DB SDK uses an Iqueryable syntax that lets you use LINQ expressions in C# to generate queries on the database. This is superb for simple queries, but it is advisable to double-check what database queries it is generating for complex queries as they may not be optimally efficient. We will use the syntax to simply return all. Delete the entire function body and replace it with this:
return client.CreateDocumentQuery<Product>(productCollectionUri, productQueryOptions).ToList();
Also, add the following using statement:
using System.Linq;
Add a Product class in a file called Product.cs in a folder called Models with properties matching the ones in the Cosmos DB. Add a JsonProperty annotation on the ID property to force it into lower case:
Add the following three using statements to the GetProducts function:
using System.Collections.Generic; using ProductsApi.Models;
Now, go to Cosmos DB in the Azure Portal and click on Keys. Retrieve the endpoint and the primary key and enter them into the DocumentClient. This is vital to authorize your function to connect to your Cosmos DB.
Test the function by pressing the play button on the debug tab and going to the address in your browser. You should see an output similar to the following when testing:
Open the ServerlessWebsite folder in VS Code. Add a table to the index.html file using the following code:
<table> <thead> <tr> <th> Name </th> <th> Size </th> <th> Colour </th> <th> Quantity In Stock </th> </tr> </thead> <tbody id='tableBody'> </tbody> </table>
Add a function that takes the object from the API and returns a table row by using the following code:
<script> function rowOfDataFromObject(data){ let row = document.createElement('tr'); let nameTableElement = document.createElement('td'); nameTableElement.appendChild(document.createTextNode(data.name)); row.appendChild(nameTableElement); let sizeTableElement = document.createElement('td'); sizeTableElement.appendChild(document.createTextNode(data.size)); row.appendChild(sizeTableElement); let colourTableElement = document.createElement('td'); colourTableElement.appendChild(document.createTextNode(data.colour)); row.appendChild(colourTableElement); let quantityTableElement = document.createElement('td'); quantityTableElement.appendChild(document.createTextNode(data.quantityInStock)); row.appendChild(quantityTableElement); return row; } </script>
Add a HTTP GET call to the Azure function either by running locally or in the cloud using the fetch method, and turn it into a table row using the JavaScript method you just created (in the following screenshot, the function being called is one that would be running locally as the address in the fetch method is a localhost address):
Append the rows to the table using the ID set in the HTML and the table rows created by the method:
document.getElementById("tableBody").appendChild(productRow);
Cross Origin Resource Sharing (CORS) is an important security measure that applies to Azure functions as well. It prevents scripts from unexpected websites calling your Azure Function. For the moment, though, this prevents us from testing effectively, so we are going to open it up to all. In production, you would give it only the addresses you expect, so this would be the URL of your website, for example. Open the ProductsApi folder and add a local.settings.json file if it isn't already there. Modify it to add an element called Host with a property called CORS with the value *:
Finally, test your HTML page by opening it from your local disk or uploading it to Azure Storage and opening in your browser:
You've successfully created your first serverless application in only a couple of hours. This solution almost certainly scales better than anything most developers have ever made and required little extra effort from us.
Activity 1: Creating a Serverless Application for Viewing User Data
You are a developer working for Serverless Ltd. and you have been tasked with creating a serverless application for a client that lets them view basic data about the end customers of their clothing company. For this purpose, you need to create an end-to-end serverless app to view users. Follow these steps to complete this activity:
Create a collection called users in the Cosmos DB database named serverless.
Add some user data to it. The example object just uses name and email address:
{ "name": "Daniel", "emailAddress": "[email protected]" }
Create an Azure function called GetUsers that reads from the users collection.
Create an index.html file that displays that data on a web page by using JavaScript to call the Azure function:
Note
The solution for this activity can be found on page 230.