Personalized short URLs look so cool. Sure, I could use a service like Bitly. They even provide the ability to use your domain. But I love building things, even when I don't need to. It allows me to explore and learn new things.
I realized I needed a short URL when I started sharing posts from my website, baldbeardedbuilder.com. It's a long one, and that's not considering slugs. So I went on the hunt for something super short and found bbb.dev. It's perfect! I purchased it quickly and got to work.
Storing URLs
First up was keeping up with the short/long URL pairs. I knew I'd be looking for a database, and having used Fauna in the past, I decided it would work well for this application. My site doesn't get a lot of traffic, so I expected their free tier to cover me.
Update: this function has been running for well over a year and I still don't come close to fully utilizing Fauna's free tier.
Data Structure
In my Fauna account, I created a new database named smolify
with a collection named ShortyMcShortLink
. Why those names? Because I like to think my dad-joke brain is clever. 😁
Next, I added an initial record. I knew it needed a short code and a full URL, but I also thought it would be cool to track how many visits that short URL had been used. So I added a visits
property to keep track of that. My first short URL would be used for the BBB community code of conduct and looked like the
following:
{
"source": "coc",
"target": "https://baldbeardedbuilder.com/code-of-conduct/",
"visits": 0
}
Whenever I need a new short URL, I log into Fauna and create a new document in that format with the corresponding source
and target
.
Retrieving Data
With data in my collection, I needed a way to grab specific records based on the source
property. To do that, I created an index named shortyMcShortLinkBySource
. Again, I apologize for my dad-joke-inspired naming conventions. 🙄
That index has one 'term' that it watches; the source
property.
Creating the Netlify Function
Once the database was ready, I moved to create a serverless function to retrieve and update data. My site is currently hosted on Netlify, so their functions seemed like a great place to start.
I created a new JavaScript file named smolify.js
and added my dependencies.
import { Client, query } from 'faunadb';
require('dotenv').config()
exports.handler = async (event) => {
}
Finding the Short Code
First, I needed to find the short code and get the long URL. To do that, I created the getLongUrl
function. It takes a path (in this case, the short-code) and finds it in the index I previously set up.
The mapResponse
function performs a little mapping to make it easier to identify the id
assigned by Fauna for later use and then return the record Fauna found. If anything goes wrong it returns undefined.
const getLongUrl = async (path) => {
try {
const response = await client.query(
query.Map(
query.Paginate(
query.Match(
query.Index("shortyMcShortLinkBySource"), path
)
),
query.Lambda("ShortyMcShortLink", query.Get((query.Var("ShortyMcShortLink"))))
)
)
if (response.data && response.data.length > 0) {
const shortUrl = mapResponse(response.data[0])
return shortUrl
}
} catch (err) {
console.log(err)
}
return undefined
}
function mapResponse(payload) {
return {
...payload.data,
_id: payload.ref.value.id
}
}
Counting Visits
Before I send the result back to the browser, I want to record the visit to Fauna. To do that, I created the recordVisit
function below. It increments the visit count that Fauna provided and then replaces that object based on the Fauna id
that we mapped.
const recordVisit = async (shortUrl) => {
try {
shortUrl.visits++
await client.query(
query.Replace(query.Ref(query.Collection("ShortyMcShortLink"), shortUrl._id), {
data: shortUrl
})
)
} catch (err) {
console.log(err)
}
}
Then I updated the getLongUrl
function to add a call to recordVisit
between mapping and returning it.
const shortUrl = mapResponse(response.data[0])
await recordVisit(shortUrl)
return shortUrl
Finishing Our Function
With the JavaScript ready, I added it to the handler function. First, it calls the getLongUrl
function with the event.queryStringParameters.path
. I'll cover changing /coc
to /?path=coc
later.
If the function can find a short URL, the redirectUrl
variable is set to its target
property. If it returns undefined, the function will redirect the person to the homepage of baldbeardedbuilder.com.
exports.handler = async (event, context) => {
const shortUrl = await getLongUrl(event.queryStringParameters.path)
const redirectUrl = shortUrl ? shortUrl.target : 'https://baldbeardedbuilder.com/'
return {
statusCode: 302,
headers: {
location: redirectUrl,
'Cache-Control': 'no-cache',
},
body: JSON.stringify({}),
}
}
Configuring Netlify
All that was left was to redirect the traffic using the short URL to the function. Luckily, Netlify makes that a pretty quick process. First, I added the domain to my Netlify application. Once it was set up and sending traffic to the site, I set up the redirect on Netlify.
Redirecting the Short URL
At the root of the site, I added a netlify.toml
file with the following snippet:
[[redirects]]
from = "https://bbb.dev/*"
to = "/.netlify/functions/smolify?path=:splat"
status = 301
force = true
This takes all traffic to https://bbb.dev
and redirects it to the smolify
function. When redirecting, it adds the splat from the from
filter as a querystring parameter named path
.
Remember the path
parameter I was grabbing in the function? This is where it's coming from.
Wrap Up
Then I deployed everything to the site. All traffic to the short URL is redirected to the Netlify function and I can add as many short codes as I need by adding a record to my Fauna database.