Building a URL Shortener with Node.Js and Redis

A URL shortener is a pretty popular application on the Web. URL shortener saves spaces so that one can send it in emails, tweet it on Twitter while still redirecting to the original URL.

For example, consider this URL:

https://www.amazon.in/Apple-iPhone-XR-64GB-White/dp/B07JGXM9WN/

If we generate the short URL of this, it will be:

sleepy-dawn-98666.herokuapp.com/bvVDmIUq

It’s simple, compact and easy to transfer.

In this tutorial, we are going to build the simple URL shortener application using Node as a backend and Redis as a Database.

You can check out the demo application running on Heroku to check what we are going to build.

Disclaimer: This is not a production-grade code. It’s for understanding and building proof of concept applications.

How URL Shortner works?

URL shortener works in two ways:

  1. Generate a short URL based on the given URL.
  2. Redirect short URL to original URL.

To short URL, we need to generate a random hash and assign it to the original URL and store it in our database. When someone requests the short URL, the system needs to look up in the database and if found, return the original URL for redirection.

Why Redis?

Because Redis is made for high volume systems like a URL shortener. Redis is a key value-based database which is a perfect use case for applications like a URL shortener.

Pre-requisites

We are going to use Node as our backend and Redis as a database. I am going to use RedisGreen as a Redis hosting service. RedisGreen provides Redis hosting with dedicated servers and 24/7 monitoring. If you are looking out for affordable to your pocket and reliable service, do check out RedisGreen.

RedisGreen

Sign up and create a RedisGreen account, it’s free for 5 days as a trial period.

Once you have created an account, you can get your Redis server credentials by clicking on a connect button.

URL shortner using Node and Redis

Our Node server will expose an API that will be consumed by front-end to generate short URLs. Our Node server will also perform Redirection. (In production, you can use Nginx to do the same job in an effective way)

System flow URL shortner

Let’s build it.

Node Server

Let’s create a fresh Node project.

mkdir urlshortner && cd urlshortner
npm init --y

Let’s install the required dependencies.

npm i --S express redis body-parser valid-url shortid cors

Once it is installed, let’s create our main application code.

app.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const validUrl = require('valid-url');
const models = require('./models');
const router = express.Router();
const app = express();

app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

router.get('/', (req,res) => {
    // send homepage
    res.sendFile(__dirname + '/views/');
});

router.get('/:url', async (req,res) => {
    // decode and redirect url
    try {
        let url = await models.findURL(req.params.url);
        if(url !== null) {
            res.redirect(url);
        } else {
            res.send('invalid/expired URL');            
        }
    }
    catch(e) {
        console.log(e);
        res.send('invalid/expired URL');
    }
});

router.post('/api/short', async (req,res) => {
    if(validUrl.isUri(req.body.url)) {
        // valid URL        
        try {
            let hash = await models.storeURL(req.body.url);
            res.send(req.hostname + '/' +hash);
        }
        catch(e) {
            console.log(e);
            res.send('error occurred while storing URL.');
        }
    } else {
        res.send('invalid URL');
    }
});

app.use('/', router);

app.listen(process.env.PORT || 3000);
console.log(`App is listening at ${process.env.PORT || 3000}`);

As you can see in the code, exposed three API. On the / request, we return our custom HTML that acts as a front-end. The second API accepts the short URL and look it up in the Redis. If found, it performs the redirection. The third API takes the original URL and converts it into a short URL.

Let’s check out the Redis code.

models.js
const shortId = require('shortid');
const redisModule = require('redis');
const redis = redisModule.createClient('---paste URL from redis green dashboard------', {
    host: '---paste URL from redis green dashboard------',
    port: ---paste URL from redis green dashboard------,
    password: '---paste URL from redis green dashboard------'
});

redis.on('connect', () => {
    console.log('Connected to RedisGreen Server');
});

redis.on('ready', () => {
    console.log('ready to work with RedisGreen Server');
});

redis.on('error', (err) => {
    console.log('Error occurred while connecting to Redis');
    process.exit(0);
});

function storeURL(url) {
    return new Promise((resolve, reject) => {
        redis.get(url, (err, reply) => {
            if(err) {
                return reject('error occurred during the redis operation');
            }
            if(reply) {
                resolve(reply);
            } else {
                // make new entry
                let id = shortId.generate();
                redis.set(id, url, 'EX', 86400);
                // set URL as a key too for searching
                redis.set(url, id, 'EX', 86400);
                // return
                resolve(id);
            }
        });
    });
}

function findURL(key) {
    return new Promise((resolve, reject) => {
        redis.get(key, (err, reply) => {
            if(err) {
                return reject('error occurred during the redis operation');                
            }
            // check if the reply exists
            if(reply === null) {
                resolve(null);
            } else {
                resolve(reply);
            }
        });
    });
}

module.exports = {
    storeURL: storeURL,
    findURL: findURL
};

In models, we have two functions, one to create a short URL, one to look it up.

While creating the short URL, we also need to check whether the short URL already exists or not (Since it’s not a SaaS app, giving different short URLs for the same URL does not make sense). If it exists, returns the short URL else creates a new one.

If you notice, we are setting two keys for each URL. Why? to reverse lookup the original URL.

bvVDmIUq ==> https://www.amazon.in/Apple-iPhone-XR-64GB-White/dp/B07JGXM9WN/
https://www.amazon.in/Apple-iPhone-XR-64GB-White/dp/B07JGXM9WN/ ==> bvVDmIUq

So when a user tries to create a short URL of the same URL, we match it with the key and return the short URL since it already exists.

Here is our front-end code, stored inside the views folder.

<!DOCTYPE html>
<html lang="EN">
  <head>
        <title>URL Shortner app using Node and Redis</title>
        <link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'>
        <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
  </head>
  <body style="background-color:#E64A19;">
      <div class="container" style="margin-top: 10%;">
            <div class='row'>
                    <h1 style='color: white'>URL Shortner App using Node and Redis</h1>
                    <input type='text' class="form-control" style="width: 50%;" id='url'>
                    <input type='button' id='submit' value='Generate Short URL' class="btn btn-primary" style="margin-left: 10px;">
              </div>
  </body>
  <script type="text/javascript">
  $(document).ready(function() {
      $('#submit').click(function() {
          var url = $('#url').val();
          $.post('/api/short', {url: url}, function(data, status) {
              alert('Short URL: ' + data);
          });
      });
  });
  </script>
</html>

You can run the app and test its working or check out the live demo.

Add any URL in and click on the “Generate Short URL” button.

URL shortner app

Conclusion

In this article, we have studied how URL shorteners work and how we can develop the proof of concept application using Node and Redis (hosted on RedisGreen).

Pankaj Kumar
Pankaj Kumar
Articles: 207