How to Build a URL Shortener with Node.js and MongoDB
Tags: mongodb, linkshortener, urlshortener, node
Published: 2018-02-27 18:47:08

---
title: How to Build a URL Shortener with Node.js and MongoDB
published: true
description: In this post, we’ll show you how to build a URL shortener like bit.ly or goo.gl using Express.js (Node.js) and MongoDB. We'll use Express.JS to build an API for shortening URLs and redirect users to the original URL, and MongoDB's flexible collection schemas for storing the URL data.
tags: MongoDB, link shortener, url shortener, Node.js
canonical_url: https://scalegrid.io/blog/how-to-build-a-url-shortener-with-node-js-and-mongodb/
---
<link rel="canonical" href="https://scalegrid.io/blog/how-to-build-a-url-shortener-with-node-js-and-mongodb/" />

<a href="https://scalegrid.io/blog/how-to-build-a-url-shortener-with-node-js-and-mongodb/" target="_blank" rel="https://scalegrid.io/blog/how-to-build-a-url-shortener-with-node-js-and-mongodb/"><img class="alignnone wp-image-2853 size-full" style="padding-top: 20px;" src="https://thepracticaldev.s3.amazonaws.com/i/nb748d4m6darbvy1ijjp.jpg" alt="How to build a URL shortener with Node.js and MongoDB" width="911" height="622" /></a>

<p style="text-align: justify;">In this post, we'll show you how to build a URL shortening service like <a href="https://bitly.com/" target="_blank">bit.ly</a> or <a href="https://goo.gl/" target="_blank">goo.gl</a> using Express.js (Node.js) and MongoDB. <a href="https://sg-url-shortener.herokuapp.com/" target="_blank">Here's a demo</a> of the final product we’ll be building through our <a href="https://scalegrid.io/mongodb.html" target="_blank">MongoDB hosting</a> platform.</p>
<a href="https://sg-url-shortener.herokuapp.com/" target="_blank" rel="https://sg-url-shortener.herokuapp.com/"><img class="alignnone wp-image-2853 size-full" style="padding-top: 20px;" src="https://scalegrid.io/blog/wp-content/uploads/2018/01/URL-Shortener-Header-1.png" alt="How to build a URL shortener with Node.js and MongoDB" width="911" height="622" /></a>
<h2><span style="font-weight: 400;">How Does a URL Shortener Work?</span></h2>
<p style="text-align: justify;">At a very high level, the URL shortener works by taking an entered URL and creating a relatively shortened version simplified into an easy to share format. The shortened hash will be generated by base-encoding an auto-incremented counter, and creates a minimum three-character hash that increases as the number of stored URLs go up.</p>
<p style="text-align: justify;">When the shortened version of the URL is visited, the service will decode the hash to fetch the original URL stored in MongoDB and then redirect your user to it.</p>

<h2><span style="font-weight: 400;">Getting Started</span></h2>
<p style="text-align: justify;">Here's a list of the technologies we'll use to build the URL shortener in this tutorial:</p>

<ul>
	<li>
<h4><a href="https://expressjs.com/" target="_blank">Express.js</a> (Node.js backend)</h4>
<p style="text-align: justify;">A web application framework for Node.js. We'll use it to build an API for shortening URLs and redirect users to the original URL.</p>
</li>
	<li>
<h4><a href="https://www.mongodb.com/" target="_blank">MongoDB</a> (storing URLs)</h4>
<p style="text-align: justify;">A NoSQL database perfect for this application. It provides a flexible schema design and is easy to get started with. In this tutorial, we'll be using a Shared MongoDB Cluster on ScaleGrid. It takes less than 5-minutes to set up, and you can create a free 30-day trial <a href="https://console.scalegrid.io/users/register" target="_blank">here to get started</a>.</p>
</li>
	<li>
<h4>HTML, CSS, JavaScript (front-end)</h4>
<p style="text-align: justify;">HTML, CSS and JavaScript will be used to build the front-end of the application that your users will use to shorten URLs.</p>
</li>
</ul>

<h2>URL Shortener Tutorial</h2>
<ol>
	<li>
<h3 style="display: inline !important;">Setup the MongoDB Database Structure</h3>
<p style="text-align: justify;">Let’s start by creating a <a href="https://help.scalegrid.io/docs/mongodb-new-cluster-shared-hosting" target="_blank">Shared MongoDB Cluster</a> on ScaleGrid. This is the easiest way to create a quick cluster, but you can also install MongoDB on your machine and get started there.</p>

<a href="https://console.scalegrid.io/users/register" target="_blank" rel="https://console.scalegrid.io/users/register"><img class="alignnone wp-image-2855 size-full" style="padding-top: 20px; padding-bottom: 20px;" src="https://scalegrid.io/blog/wp-content/uploads/2018/01/URL-Shortener-MongoDB-Cluster.jpg" alt="Create a MongoDB Shared Cluster: Build URL Shortener - ScaleGrid Blog" width="1919" height="883" /></a>
<p style="text-align: justify;">Once the cluster is created, you'll be provided with a <a href="https://help.scalegrid.io/docs/mongodb-connecting-to" target="_blank">connection string</a> that can be copied with a single click from your Cluster Details page. We'll need this string to connect to the cluster from our application. Remember, never share your connection string with anyone.</p>
<p style="text-align: justify;">We'll need two collections for the URL shortener:</p>

<ul>
	<li>
<h4><b>Collection 1</b></h4>
<p style="text-align: justify;">A collection to store the URL and dynamically generated ID:</p>

<a href="https://scalegrid.io/blog/wp-content/uploads/2018/01/Table-Collection1.png"><img class="alignnone size-full wp-image-2861" src="https://scalegrid.io/blog/wp-content/uploads/2018/01/Table-Collection1.png" alt="Collection 1: Store the Shortened URL and Dynamically Generated ID" width="640" height="221" /></a></li>
	<li>
<h4><b>Collection 2</b></h4>
<p style="text-align: justify;">A collection to maintain the counter that will be auto-incremented when a new URL is stored in the previous collection. A new document is created in the previous collection with this newly incremented counter:</p>

<a href="https://scalegrid.io/blog/wp-content/uploads/2018/01/Table-Collection2.png"><img class="alignnone size-full wp-image-2862" src="https://scalegrid.io/blog/wp-content/uploads/2018/01/Table-Collection2.png" alt="Collection 2: Maintain the Auto-Incremented Counter for Storing Shortened URLs" width="637" height="81" /></a></li>
</ul>
<p style="text-align: justify;">It's important to note that we're not storing the hashes anywhere in the database. The hash will be base-encoded and decoded dynamically with the general algorithm that will result in the unique ID stored in the first collection. This ID will then fetch us the original URL that the user will redirect to.</p>
<p style="text-align: justify;">For this tutorial, we'll be using the common base64 encoding and decoding mechanism for generating our unique shortened hash. For more information on encoding/decoding strings using base64, check our the following <a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding" target="_blank">MDN Web Doc</a>.</p>
</li>
	<li>
<h3><span style="font-weight: 400;">Set Up the Express.js Backend</span></h3>
<p style="text-align: justify;">Here's a list of dependencies required to set up our Node.js backend:</p>

<ul>
	<li>express (base app)</li>
	<li>body-parser (add-on for parsing data sent over HTTP requests)</li>
	<li>btoa (base64 encoding)</li>
	<li>atob (base64 decoding)</li>
	<li>dotenv (storing connection string in a .env file for development purposes)</li>
	<li>mongoose (adapter for MongoDB on Node.js)</li>
</ul>
<p style="text-align: justify;">Here is a sample version of the package.json that you can use to set up the app:</p>

<pre>{
  "name": "sg-url-shortener",
  "version": "1.0.0",
  "description": "A simple URL shortener built with Node.js and MongoDB",
  "dependencies": {
    "atob": "^2.0.3",
    "body-parser": "^1.15.2",
    "btoa": "^1.1.2",
    "dotenv": "^4.0.0",
    "express": "^4.10.2",
    "mongoose": "^4.13.7"
  },
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "engines": {
    "node": "4.8.4"
  }
}
</pre>
<p style="text-align: justify;">Run “npm install” to install all the required dependencies.</p>
<p style="text-align: justify;">Once all of our dependencies have been set up, we need to connect to our Shared MongoDB Cluster. Create an .env file in the root of the project and add the connection string to it. You can get the connection string from your Cluster Details page under the Overview tab on the ScaleGrid Console.</p>

<pre>connectionString=mongodb://user:password@devservers</pre>
<p style="text-align: justify;">Before we start writing code, it's a good practice to visualize the app flow so we have a good understanding of how the shortening process will work. Here's a diagram showing the process of URL shortening:</p>

<a href="https://scalegrid.io/blog/wp-content/uploads/2018/01/Process-Storing-URLs.png"><img class="alignnone size-full wp-image-2857" style="padding-top: 20px; padding-bottom: 20px;" src="https://scalegrid.io/blog/wp-content/uploads/2018/01/Process-Storing-URLs.png" alt="App Visualization Flow Chart: Shortening URLs - ScaleGrid Blog" width="718" height="611" /></a>
<p style="text-align: justify;">Here's a diagram showing the process of redirection when a shortened URL is visited:</p>

<a href="https://scalegrid.io/blog/wp-content/uploads/2018/01/Process-Redirecting-Users.png"><img class="alignnone size-full wp-image-2858" style="padding-top: 20px; padding-bottom: 20px;" src="https://scalegrid.io/blog/wp-content/uploads/2018/01/Process-Redirecting-Users.png" alt="App Visualization Flow Chart: Redirecting Shortened URLs - ScaleGrid Blog" width="476" height="541" /></a>
<p style="text-align: justify;">Now that we've visualized the entire process, it's time to translate the above flowcharts into code.</p>
</li>
	<li>
<h3><span style="font-weight: 400;">Initializing the Application</span></h3>
<p style="text-align: justify;">Before we start writing business logic, we need to initialize our application with our node modules and set up a server.</p>
<p style="text-align: justify;">Load .env files only in dev mode. Since the demo application is hosted on <a href="https://www.heroku.com/home" target="_blank">Heroku</a>, an environment variable has been created from the Heroku dashboard that already contains the connection string there:</p>

<pre>if(process.env.NODE_ENV !== 'production') {
    require('dotenv').load();
}</pre>
<p style="text-align: justify;">Application initialization, server and middleware setup. Note that we are also getting the connection string from the environment variable:</p>

<pre>var express = require('express'),
    bodyParser = require('body-parser'),
    app = express(),
    http = require('http').Server(app),
    mongoose = require('mongoose'),
    btoa = require('btoa'),
    atob = require('atob'),
    promise,
    connectionString = process.env.connectionString,
    port = process.env.PORT || 8080;

http.listen(port, function() {
    console.log('Server Started. Listening on *:' + port);
});

app.use(express.static('public'));
app.use(bodyParser.urlencoded({
    extended: true
}));</pre>
<p style="text-align: justify;">Base-route for loading up the front-end of our app:</p>

<pre>app.get('/', function(req, res) {
    res.sendFile('views/index.html', {
        root: __dirname
    });
});
</pre>
</li>
	<li>
<h3><span style="font-weight: 400;">Storing URLs in MongoDB</span></h3>
<p style="text-align: justify;">Let’s start by creating the collection schemas for storing data. As discussed above, we need two collections: one for storing the auto-incremented counter and the other for storing the URLs.</p>

<pre>var countersSchema = new mongoose.Schema({
    _id: { type: String, required: true },
    count: { type: Number, default: 0 }
});

var Counter = mongoose.model('Counter', countersSchema);

var urlSchema = new mongoose.Schema({
    _id: {type: Number},
    url: '',
    created_at: ''
});

urlSchema.pre('save', function(next) {
    console.log('running pre-save');
    var doc = this;
    Counter.findByIdAndUpdate({ _id: 'url_count' }, { $inc: { count: 1 } }, function(err, counter) {
        if(err) return next(err);
        console.log(counter);
        console.log(counter.count);
        doc._id = counter.count;
        doc.created_at = new Date();
        console.log(doc);
        next();
    });
});

var URL = mongoose.model('URL', urlSchema);
</pre>
<p style="text-align: justify;">The above code creates the two collections and sets up our database for storing these collections. We're also using a pre-save hook for the URL schema since we need to auto-increment the counter and log the date and time at which the URL was created.</p>
<p style="text-align: justify;">Next, we need to make sure that we start our application fresh and all previous entries are deleted. Once we reset, we'll initialize our counter with a starting value of 10,000 to set up the URL shortening process. You may start with any value. This was chosen at random and will auto-increment by a value of one.</p>

<pre>promise = mongoose.connect(connectionString, {
    useMongoClient: true
});

promise.then(function(db) {
    console.log('connected!');
    URL.remove({}, function() {
        console.log('URL collection removed');
    })
    Counter.remove({}, function() {
        console.log('Counter collection removed');
        var counter = new Counter({_id: 'url_count', count: 10000});
        counter.save(function(err) {
            if(err) return console.error(err);
            console.log('counter inserted');
        });
    });
});
</pre>
<p style="text-align: justify;">Our application is now ready to start accepting and shortening URLs! Let’s create a POST API that our front-end will use to send the URL:</p>

<pre>app.post('/shorten', function(req, res, next) {
    console.log(req.body.url);
    var urlData = req.body.url;
    URL.findOne({url: urlData}, function(err, doc) {
        if(doc) {
            console.log('entry found in db');
            res.send({
                url: urlData,
                hash: btoa(doc._id),
                status: 200,
                statusTxt: 'OK'
            });
        } else {
            console.log('entry NOT found in db, saving new');
            var url = new URL({
                url: urlData
            });
            url.save(function(err) {
                if(err) return console.error(err);
                res.send({
                    url: urlData,
                    hash: btoa(url._id),
                    status: 200,
                    statusTxt: 'OK'
                });
            });
        }
    });
});</pre>
<p style="text-align: justify;">As outlined in the flow diagram, once a valid URL is received, we check for its existence in the database.</p>
<p style="text-align: justify;">If found, we decode the corresponding _id field and return the hash back. Our front-end constructs the shortened URL and presents it to the user for redirection.</p>
<p style="text-align: justify;">If no URL is found, we save a new document in the collection. Remember, a pre-save step is run everytime the URL is saved. This will auto-increment the counter and log the current date and time. After the document is added, we send the hash to our front-end which constructs the shortened URL and presents it to the user for redirection.</p>
</li>
	<li>
<h3><span style="font-weight: 400;">Redirecting Users</span></h3>
<p style="text-align: justify;">We're almost done! Once our shortened URLs have been created, we need a way to redirect the user when a shortened URL is visited.</p>

<pre>app.get('/:hash', function(req, res) {
    var baseid = req.params.hash;
    var id = atob(baseid);
    URL.findOne({ _id: id }, function(err, doc) {
        if(doc) {
            res.redirect(doc.url);
        } else {
            res.redirect('/');
        }
    });
});</pre>
<p style="text-align: justify;">The above code looks for a hash in the shortened URL, base64 decodes it, checks if that ID is present in the collection, and redirects the user accordingly. If no ID is found, the user is redirected to the homepage of the URL shortener.</p>
<p style="text-align: justify;">For front-end code, please check out the GitHub repository mentioned at the end of this post. It's essentially a text box field with a button to send the URL to the back-end and is out of the scope of this article.</p>
</li>
</ol>
<h1><span style="font-weight: 400;">More URL Shortener Enhancements</span></h1>
<p style="text-align: justify;">And we're done! We have a bare-bones URL shortener that can be used internally to simplify your links. If you’d like to add more bells and whistles, here is a list of things you can additionally implement:</p>

<ul>
	<li>Better code splitting</li>
	<li>Better/custom shortening algorithm for a smaller character hash (e.g. base52)</li>
	<li>Sharing shortened URLs on social media</li>
	<li>One-click URL copy</li>
	<li>Custom hashes</li>
	<li>User registration and associate shortened URLs</li>
</ul>
The entire code is available here: <a href="https://github.com/Scalegrid/code-samples/tree/sg-url-shortener" target="_blank">ScaleGrid URL Shortener Code Samples - Github</a> A demo application is hosted on Heroku: <a href="https://sg-url-shortener.herokuapp.com/" target="_blank">ScaleGrid URL Shortener Demo</a>
<p style="color: #121315; text-align: justify;">As always, if you build something awesome, do tweet us about it <a style="font-weight: 600; color: #272f37;" href="https://twitter.com/scalegridio" target="_blank">@scalegridio</a>. If you need help with <a href="https://scalegrid.io/mongodb.html" target="_blank">MongoDB</a> or <a style="font-weight: 600; color: #272f37;" href="https://scalegrid.io/redis.html" target="_blank">Redis hosting</a> and management, reach out to us at <a href="mailto:support@scalegrid.io" target="_blank">support@scalegrid.io</a>.</p>
<p>&nbsp;</p>