A $0/month URL-Shortener on Firebase Hosting

I love sharing links, but I hate remembering them. Sometimes I can use a public service like bit.ly, but most of the time I prefer having something more personal on a custom domain.

That way I could make links like this....

example.com/tiny-phone  

Then have it redirect so something much longer (and harder to type) like an amazon link.

We can make this service using Firebase Hosting and Firebase Realtime Database. This isn't only a fast way to build a link-shortner, it's also a very cheap way. This entire project could fit within Firebase's $0 Spark Plan

Let's dive in!

Step 1: Create a Firebase project

If you've never used Firebase, you'll need to head over to the Firebase Console and hit the Create a New Project button.

Once you've created a project, you'll be see this screen.

Firebase Console

Your project name is in the top-left. Make sure to note this name as we'll need it to set up our project in Step 3.

Step 2: Install Firebase Tools

We can make this link shortner using only HTML and Javascript, so you wont need any special software except for the Firebase Tools which will allow us to initialize our project and deploy it to Firebase.

For detailed installation and setup information, you can check out the "Setup" section of the Firebase CLI Reference.

The short version is that you'll need to first install Firebase Tools via npm

npm install -g firebase-tools  

Then you'll need to login by running...

firebase login  

Once you've logged in, we're ready to start making our project.

Step 3: Initialize our Project

We need to make an empty directory to initialize our Firebase project in, so let's make that first.

mkdir url-shortner  
cd url-shortner  

You can name this directory anything, it doesn't matter at all.

Now in that directory we can initialize our project.

firebase init  

This will show a very fancy welcome screen which let's you pick which features of Firebase you want to use.

Firebase CLI welcome

We'll be using Hosting and Database. You can use the arrow keys to move down and hit space to de-select Functions, then hit enter.

Next you'll be asked to choose your project.

Choose your project

Look for your project name (the one we noted in Step 1) in this list. Find your project and hit enter.

At this point we'll be asked three questions...

? What file should be used for Database Rules?
[HIT ENTER]
? What do you want to use as your public directory?
[HIT ENTER]
? Configure as a single-page app (rewrite all urls to /index.html)? 
Yes  

Then we're done! The default settings we chose for the first two questions will create a directory named public/ and a rules file name database.rules.json.

The final question asked us about rewriting all urls. This means that any URL we request on Firebase Hosting will route to our index.html file. As the tool said, this is useful if you have many different "pages" which are all in the same HTML file. You might use this for single-paged apps. If this doesn't make much sense, don't worry - we'll come back to it below.

Step 4: Add our first shortened link to our Database

We need to set our first shortened link in the Database. This is important both for testing and so we understand our Database structure so we can secure it. The data structure we'll use looks like this...

{
  "share": {
    "tiny-phone": "https://amazon.com/...",
    "second-link": "https://google.com/....",
  }
}

As you can see, we have a top-level share node and underneath that we have shortened keys which relate to longer links.

In our JavaScript, we'll get a key from the URL, then use that to look into our Database to find the full URL.

To get started, let's just add a simple link.

firebase database:set /share/abe --data='"https://twitter.com/abeisgreat"'  

Note: You need to use double quotes inside of your single quotes. Without these quotes, the command will throw a funny HTTP Error 400

If you go to your Firebase Console, select your project then go to the Database Tab, you'll see your data is now stored in Firebase.

If you'd like, you can add more links using the Console or via Firebase Tools. Both options work fine for adding, editing, and removing data.

Step 4: Add Security Rules

We just wrote a link to our database, now we need to ensure that no one changes that link. We'll enforce this using Firebase Security Rules.

Security Rules are a complex topic - and I'm not going to cover every detail here. The important thing to understand is that these rules restrict who can read/write data in your database. These rules to not impact who can access the content you upload to Firebase Hosting. These rules only apply to your Firebase Database.

With that in mind, open database.rules.json in your favorite text editor (if you don't have a favorite, I suggest using Atom).

You'll see that the rules file contains the default Firebase Realtime Database Security Rules - no reading or writing unless the user is authentication

{
  ".read": "auth != null",
  ".write": "auth != null"
}

Modify your rules to look like this...

{
  "share": {
    "$short_link": {
      ".read": true,
      ".write": "auth != null"
    } 
  }
}

See how our rules data mimicks the /share/$short_link structure that we wrote into our Database? This is intentional. We know how our data is structured in the Database, so we can write rules which only allow users to read links, not edit them or create new ones.

Step 5: Write the Code

Finally, let's open up the public/index.html file. Delete all the example HTML that was put in by the initialization process and write this in it's place.

<html>  
<body>  
<script>  
/*
IMPORTANT: Replace this with your Firebase Project ID.  
If you go back to the Firebase Console, your Project ID  
will be in the URL like...  
https://console.firebase.google.com/project/<FIREBASE_PROJECT_ID>  
*/
const FIREBASE_PROJECT_ID = "<FIREBASE_PROJECT_ID>"

// Add a custom error message if you'd like
const ERROR_MESSAGE = "Unknown shortlink!"

/*
If we start with a URL like  
var url = "https://abe.today/tiny-phone"  
*/
var url = document.location;

/* 
Then we load the URL's path  
like "/tiny-phone" and split it  
to get ["", "tiny-phone"]  
*/
var path = url.pathname.split("/");

// Then pick out the last path chunk, in this case "tiny-phone" 
var id = path.slice(-1)[0];

/*
We can use this ID to make a Firebase REST  
API URL.  
*/
var url_parts = [  
  'https://', 
  FIREBASE_PROJECT_ID, 
  '.firebaseio.com/share/', 
  id
];
var request_url = url_parts.join("") + ".json";

/*
We wont actually use the Firebase JavaScript SDK  
because it's overkill for this and will slow down  
the redirect. We'll just make an HTTP call to the REST API.  
*/
var xhr = new XMLHttpRequest();

/*
Add a event handler so we can detect when the  
data has been loaded.  
*/
xhr.onreadystatechange = function() {  
    /* 
    If the state is done, the data is loaded 
    and it's time to redirect
    */
    if (xhr.readyState == XMLHttpRequest.DONE) {
        // Parse out the URL from the JSON we get from the API
        var redirect = JSON.parse(xhr.responseText);
        // Then try to redirect the user!
        if (redirect && !redirect.error) {
          document.location = redirect;
        } else {
          // If it fails, throw out an error
          document.write(ERROR_MESSAGE)
        }
    }
}

// Here we actually initialize the HTTP request
xhr.open('GET', request_url, true);  
xhr.send(null);  
</script>  
</body>  
</html>  

I'll leave the commentary here light, just follow the code comments to understand what we're doing. The gist of the shortener is that it loads the current URL, parses out the key that matches a key in our Realtime Database then makes an HTTP request to load that data from the Realtime Database and redirects the page to the returned URL.

Step 5: Test it!

We can run this whole project locally be using the Firebase Tools' serve command.

Note: Make sure you're in the same folder as your firebase.json file, otherwise it may behave unexpectedly!

firebase serve  

This will create a local web server at http://localhost:5000. If you open that page, you'll see it runs our code and it provides the Unknown Shortlink error.

Now try visiting http://localhost:5000/abe. If you've set up everything correctly, you'll be redirected to my twitter! How cool is that.

Step 6: Deploy it!

We're getting into the easy steps now. Time to deploy to Firebase Hosting. Open up the Hosting tab in your Firebase Console. You'll see a greeting like this.

Firebase Hosting Tab

You don't actually need to hit the "Get Started" button - we've already done all that. We can hop right back to the command line.

firebase deploy  

This will take a few seconds and then it will provide you with a long URL which is your *.firebaseapp.com domain.

Don't bother copy and pasting it, just run this to open the page in a web browser.

firebase open hosting:site  

After this you'll see the exact same thing you saw from firebase serve! If you add /abe to the URL you'll be redirected - magic!

If you go back to the Hosting tab in your Firebase Console, you'll see the deployment history has at least one deployment. (if you made mistakes and had to redeploy, like me, you'll have several deploys)

Step 7: [Optional] Add a Custom Domain

We're basically done. The absolute last step, if you'd like, is to add a Custom Domain so that your site will show up under your example.com instead of YOUR_FIREBASE_APP.firebaseapp.com.

I wont go through the whole process here, but you can follow the instructions for Custom Domains in Firebase Hosting and you'll be on a custom domain in no time.

We're done! Next Steps?

I've deployed this exact link shortener on my personal domain abe.today. I modified mine slightly to serve under the share path, so my links look like abe.today/share/twitter instead of abe.today/twitter, but the code is the same - just a different file structure.

If you want to expand on this project, I'd put together a UI to add new shortened links and modify existing ones. That would be fun!

I hope you enjoyed this. Please let me know if you enjoyed this and would like to see more projects like this!


Follow me on Twitter