Building an URL shortener is much easier now using the Serverless technologies in Azure. In this post, we will discuss about developing this solution.
Update: My colleague (Christopher Maneu) pointed out that we could optimize the cost by using Azure Static Web Apps (SWA) for hosting the management console. I updated the design to use SWA and added another API to check what the target URL is for the specific short URL gets resolved without visiting the site.
Here is the overall architecture of the solution
These are my priorities I set for this solution:
- Use Serverless component, which inherently makes it scalable & resilient.
- Make sure it is secure.
- Optimize it for cost.
- Use Infrastructure as code.
To create a short URL we need to generate a unique hash. Azure Cosmos DB for NoSQL is the most suitable datastore for storing the hash, original URL, and additional required data. We will use Azure Functions consumption plan to generate the short URLs triggered by the events to generate the URL. Azure API Management is the logical choice for APIs, for this use case consumption tier will be suitable. We need a management console to view the existing URLs of the user which can be hosted as a Single Page Application (SPA) on Azure Static Web Apps. We will use Azure Front Door to deliver the SPA and API to the end users. Authentication to the Management Console will be supported by Azure AD B2C.
User Authentication:
In the Azure AD B2C, create a User Flow of type “Sign up and sign in”. In the user flow, for user attributes include Email Address, Given Name, and Surname and for application claims include Email Addresses, Given Name, Surname, and User’s Object ID attributes. Register two app clients, one for SPA and another for API. In API app client, add two scopes API Write & API Read under Expose an API setting. In SPA app client, under API permissions add the two scopes that we created in API app client. For step by step instruction on configuring Azure AD B2C, check this documentation. Note down the name of the Azure AD B2C tenant and User Flow, Client ID of API app client and SPA app client, and fully qualified URL of the API Write & API Read scopes as we will need them while deploying the solution.
Scalable Frontend:
I’m using Azure Front Door as the CDN with only HTTPS enabled and using TLS 1.2. Using Rule set, rules are configured to route the request to the APIs hosted in API Management or SPA hosted in SWA. Azure Storage Blob origin is connected to Front Door through Private Link hence it won’t be accessible from network outside of it. To secure the API endpoint, API Management policy is configured to validate that the request contains the HTTP Header X-Azure-FDID
which is set to the ID of our Front Door instance. This makes sure that the APIs will accept requests only from our own Front Door and nowhere else.
Serverless Backend:
Datastore: We will create a Serverless Cosmos DB account with zone redundancy to support resiliency over zonal failures. We will create a Cosmos DB container with /id
as the partition key. Unique hash that will be generated for every URL will be stored as the value of id which follows the Cosmos DB best practice of having high cardinal value for partition key.
APIs: We need at least 4 APIs, create short URL, get the original URL from short URL, list all the URLs specific to a user, and delete a specific URL. API Management supports validating the JWT token generated by Azure AD B2C after the user authentication.
API Management policy also supports making direct REST API call to Cosmos DB service and we will directly access Cosmos DB to get the original URL and Delete an URL. Using the authentication-managed-identity
policy we will authenticate with the backend service (in our case Cosmos DB). Update the backend service to Cosmos DB endpoint using the set-backend-service
policy. Cosmos DB REST API call requires certain headers (e.g., Authorization, x-ms-date, x-ms-version, and x-ms-documentdb-partitionkey) which can be set using set-header
policy. Values for few of these header (e.g., Authorization, x-ms-documentdb-partitionkey) needs to be formulated by us and we will use policy expressions to generate or obtain them. Cosmos DB REST API call should target the specific URI, we will use rewrite-uri
policy to point to the expected URI.
Link Checker API: This API lets you check what target URL does a particular short URL resolve to. You might want to check whether a URL is legit before visiting it and this is an extremely useful capability.
Compute: We need Azure Function to create the short URL where we need to generate unique hash for the short URL and list URLs as we need to verify whether the original URL is still accessible. I have written a short python-based Azure Function to generate a hash, validate that it’s unique among the existing data. I have added the original URL and the object ID of the user (based on the value in JWT token) who created the short URL. Similarly, another Azure Function is used to get the list of short URLs created by specific user and validate whether that URL is still accessible to detect broken links. These two functions are deployed in a single Function App.
Update: Based on the valuable feedback from my colleague Darko Todoroski. I have updated the list URLs API to use an Azure Function and validate whether the original URL is accessible or broken. This will help us to identify the broken URLs and delete them.
This is how the management console looks like
Link Checker UI:
Code Example:
Example implementation of this solution is available in Serverless URL Shortener on Azure GitHub repository.
This repository contains the following:
- Single Page Application (SPA) [HTML/JavaScript]
- Azure Function code to create the short URL [Python]
- Deployment script which enables you to deploy the complete solution [Python]
- Example configuration file used by the deployment script [YAML]
- Bicep template to create the required infrastructure [Bicep]
Here is a quick summary of forecasted cost per month in Azure West Europe (Amsterdam) region.
Service | Price Dimension (per month) | Cost |
---|---|---|
Storage Account | Blobs (1GB) | 0.32 |
Static Web Apps | Free | 0 |
API Management | 1MM requests | 0 |
Function | 100K Executions | 0 |
CosmosDB | 1MM requests & 1GB storage | 0.31 |
Azure Front Door | Premium 5GB data transfer | 35** |
Application Insights | Ingestion & Storage | 3.0 |
Total | 38.63 |
** Initial design used Azure Front Door Premium, whereas while using Azure Static Web Apps we don’t need Premium subscription hence using Standard. I could have used only Static Web Apps (like this solution) with Functions as API backend. But I decided to use API Management as it has robust capabilities and just to perform the URL redirect using Functions is not cost effective. With Front Door it caches most of the requests hence offers better performance and reduced cost when we scale.