Build a Simple URL Shortener with CloudFront KeyValueStore
1. Introduction
AWS announced the general availability of CloudFront KeyValueStore a while back. This global key-value datastore greatly increases the capabilities of CloudFront Functions. Use cases include A/B testing, handling feature flags, and more. This blog post will teach you how to build a simple and fully serverless URL shortener using the new feature.
2. CloudFront Functions and KeyValueStore
CloudFront functions is a capability of CloudFront that lets you run custom code to customize how your content is served. Like Lambda@Edge, it allows you to run code at the edge, closer to your users. Functions offer a more lightweight option than Lambda@Edge, perfect for use cases like URL rewriting, redirecting, or header manipulation.
CloudFront functions can handle extreme traffic at low latency but has some drawbacks. For example, they disallow network calls and file system access. For a comparison between CloudFront functions and Lambda@Edge, refer to the official AWS documentation.
CloudFront functions do not support environment variables. Before, this meant there was no way to configure a function dynamically. For example, if you wanted to create a URL shortener with only CloudFront functions, you must redeploy the function to add a new mapping. This had multiple drawbacks:
- Redeploying CloudFront resources can take a long time.
- There is a size limit of 10KB for CloudFront functions, limiting the maximum number of mappings.
With the introduction of CloudFront KeyValueStore, you can now separate your code and configuration. This datastore is replicated globally and can be accessed with low latency from any edge location. In the URL shortener example, you can now store the mappings in the KeyValueStore and update them without redeploying the function.
CloudFront functions can be added to a CloudFront distribution in two ways:
- Viewer request functions are invoked before CloudFront checks its cache.
- Viewer response functions are invoked after CloudFront checks its cache.
We will implement our URL shortener with a viewer request function and a dummy origin. Whenever a request is made to the distribution, the function will query the KeyValueStore for a match. If there is a match, it will redirect the user to the stored URL. If no match is found, it will redirect the user to a fallback URL.
3. Creating a simple URL shortener
Let’s get building. The code samples below include regular CDK constructs, but they are used in an SST context. You might have to adjust the code samples slightly if you are a CDK user. If you haven’t tried SST yet, I highly recommend you check it out. The Developer Experience is fantastic, and you can always fall back to CDK constructs if needed.
Note
Support for CloudFront KeyValueStore was added in CDK version 2.118.0. At the time of writing, SST is bundled with version 2.110.1. If you are using SST, you must install a newer version of CDK.
3.1 Adding a CloudFront KeyValueStore
First, we add a CloudFront KeyValueStore to our stack. This KeyValueStore will hold the mappings between short and long URLs.
3.2 Adding a CloudFront Function
Next, we add a CloudFront Function. This function will be invoked whenever a request is made to our CloudFront distribution. For example, when a request is made to https://example.com/short-url
, the function will query the KeyValueStore for short-url
. If there is a match, the function will return a redirect to the corresponding URL. The function will redirect to a default page if there is no match.
The function code looks like this:
Note that CloudFront functions run on a special runtime. This runtime is very restrictive in what you can do. Most notably, you may not access the file system or perform network calls.
In the code above, we need a way to inject the ID of the KeyValueStore in place of KEY_VALUE_STORE_ID_PLACEHOLDER
. CloudFront functions do not support environment variables, so we must hard-code the ID in the function code. We can use CDK to inline it for us when deploying.
In the stack code, add the following:
First, we read the function code from the file and replace the placeholder with the actual KeyValueStore ID. We then create the function and specify the JS_2_0
runtime, which is required to utilize the KeyValueStore.
Lastly, we must associate the KeyValueStore with the function. The construct does not yet support this, so we use an escape hatch to add a raw override.
3.3 Adding a CloudFront Distribution
Finally, add the CloudFront distribution and associate the function as a viewer request function.
You may enter any origin here, as it will never be used. The function will always return a redirect to either a found match or the default URL.
3.4 Full stack code
The stack now looks like this:
Not much code for a fully functional URL shortener. Let’s deploy it and see it in action.
As usual, deploying CloudFront resources can take a while. This is where the KeyValueStore can help us. By storing the configuration (the URL mappings) in the KeyValueStore, we can update the mappings without having to redeploy the CloudFront function.
4. Showtime
After deploying the stack, take note of your auto-generated distribution URL. It should look something like this: https://abcdef123456.cloudfront.net
.
Open a browser and navigate to the URL. Since you haven’t added any mappings, you should be redirected to the default URL you specified in the function code. In my case, it’s https://www.eliasbrange.dev
.
Go to the CloudFront console and add some mappings. To find it, go to CloudFront -> Functions -> KeyValueStores.
Click on the KeyValueStore you created earlier, you should see the following screen:
Click edit in the Key value pairs section and add a few mappings. I’m going to add the following three:
With these in place, if I navigate to my CloudFront URL and append /momento
I should be redirected to my blog post about testing EventBridge using Momento Topics.
It works! With a few lines of code and a couple of CloudFront resources, you now have a functioning URL shortener. While using the lengthy CloudFront URL doesn’t make sense for a shortener, you can add a custom domain to your distribution.
In my case, I got the domain elbr.dev to create nice-looking short URLs, such as:
- elbr.dev/in -> LinkedIn
- elbr.dev/gh -> GitHub
- elbr.dev/p-url -> This post
5. Tracking stats with CloudWatch
The setup is quite basic and missing some important features. For one, tracking how often each short URL is used. You could then share a blog post with different short URLs on different social networks and measure which one gets the most traction.
As mentioned, CloudFront functions run on a special runtime without network access. This means you cannot increment a counter in a database or send an event directly from the function. Instead, we will take advantage of the logging capabilities of CloudFront functions.
One advantage CloudFront functions have over Lambda@Edge is that all logs are consolidated in a single region. In Lambda@Edge, logs are spread across all regions where the function is replicated. This makes it hard to query logs across all regions. CloudFront functions instead send all logs to a log group named /aws/cloudfront/function/<FunctionName>
in the us-east-1 region, no matter which edge location ran the function.
After making a few requests to the distribution, the log group contains the following:
Using the parse
and stats
functions in Logs Insights, we can extract the short URL and count the number of times each has been requested during the selected time period:
Run the query and then select the Visualization tab to see the results:
It may not be the flashiest solution, but it gets the job done.
6. Conclusion
In this blog post, you have learned how to use CloudFront Functions and KeyValueStore to create a simple URL shortener. You have also seen how to track usage statistics using CloudWatch Logs Insights.
It certainly isn’t the most advanced URL shortener out there. But it is cheap and gets the job done, and being fully serverless means there’s little to maintain.