Build and Deploy URL Shortener to custom domain from scratch -  Node JS

Build and Deploy URL Shortener to custom domain from scratch - Node JS

ยท

13 min read

Let's build a URL shortener ( Minii in my case ) using Node JS (Express JS)

Structure:

minii_stack.PNG

  • Express JS
  • Mongo DB Atlas as database
  • Mongoose JS to handle MongoDB
  • ejs

Let's make our hands dirty..

  1. Create Folder with project name ( Minii in my case )
    npm init in terminal and enter your details like below

    • You can leave everything default
    • I chose server.js as entry point by default it is index.js
package name: (minii)
version: (1.0.0)
description: Custom URL shortener
entry point: (index.js) server.js
test command:
git repository:
keywords:
author: Rajasekhar Guptha
license: (ISC)
About to write to E:\WebD\minii\package.json:

{
  "name": "minii",
  "version": "1.0.0",
  "description": "Custom URL shortener",
  "main": "script.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Rajasekhar Guptha",
  "license": "ISC"
}


Is this OK? (yes)
  1. Let's design our site next.. I made this simple design using Bootstrap minii_proto.PNG

    • As we are going to use ejs
    • npm install ejs and npm install express in terminal ( documentation here )
    • create public and views folders.
    • then create index.ejs file in views folder to design our page
    • and then add css files inside public folder
    • all these html and css files will be there at the end of this post
    • create server.js in the root directory
      • now we have to setup express and ejs as view engine
const express = require("express");

// app setup
const app = express();

app.set("view engine", "ejs");
  • now define static folder to express
    • all our css files and assets were static files and we have to tell express about them to treat them as static
const express = require("express");

// app setup
const app = express();

app.set("view engine", "ejs");
// views folder
app.set("views", __dirname + "/views");

// setup static folder
app.use(express.static(__dirname + "/public"));
  • Now we need to assign the port for our app to run
  • During development stage we can hardcode 3000 or 5000 But in production stage we cannot decide & hardcode because it will be allotted dynamically But we can get the assigned port using

process.env.PORT

  • it is null if the app is not in production stage, so the logic is

var port = process.env.PORT;
if (!port) {
  port = 3000;
}
app.listen(port, function () {
  console.log("Server is up on port : " + port);
});
  • The basic setup is over. Now we will start catching requests for our page Firstly catch get request to our home page

  • For this we need body-parser, setup goes like this

const bodyParser = require("body-parser");
....

// to get url details we need this
app.use(bodyParser.urlencoded({ extended: true }));

Now we are ready to catch url requests

  • app.get("path",callback fun) for get req
  • app.post("path",callback fun) for post req General representation for callback function is

      (request, response) => {
    
     });
    
  • Request arg contains details of request
  • We will send our result using response arg

in our case when we received a get req for our home page we want that index.ejs to be rendered and displayed.. So,

app.get("/", (request, response) => {
  response.render("index");
});

Now we can test our home page using

run node server.js and head to localhost:3000

Yeah..! ๐ŸŽ‰We completed our first major step โœจ

There is one problem with "node server.js" i.e whenever we made some changes to script to make them apply we need to restart the server every time. So I suggest using "nodemon" - automatically restarts the app whenever file changes in the directory are detected. Install it as development dependcency as we don't need this in production environment. ' npm install --save-dev nodemon '

From now onwards use " nodemon server.js " instead of "node server.js" to start app.

We finished our setup and let us look at core functionality

Working behind the scene

  1. Get the url to be shortened from the user
  2. Assign the random ( or user requested ) short string
  3. Store short string and original url in databse with short string as Primarykey because shorturl must be unique
  4. Whenever we received a request to path similar to /shortstring check the database for respective original url and redirect to that. If doesn't exist return 404 error
  1. Getting the URL to be shortened add form to home page with method post and action to /process. ( action path is your wish )

           <form
           action="/process"
           method="post"
         > <input name="fullUrl"></input><input name="shortUrl"></input>
        </form>
    
    • Whenever user submit form we can catch & process the request in server.js file like this

        app.post("/process", (request, response) => {
      
       }
      
    • user filled values can be obtained from request arg like
       request.body.name  - name : given for input fields
    
      // In our case 
       request.body.fullUrl    
       request.body.shortUrl
    
    • We can check this
          app.post("/process", (request, response) => {
            console.log(request.body.fullUrl);
            console.log(request.body.shortUrl);
         }
      
      We are able to get user request now ๐ŸŽ‰
  2. Let us add Database to our app now

    • I prefer to use mongodb database in Mongo Atlas ( check setup here )
    • Install mongoose

      • npm install mongoose
      • setup mongoose in app

        
          const mongoose = require("mongoose");
          // mongo atlas setup
        mongoose.connect(  
        "mongoose_link",
        {
         useNewUrlParser: true,
        useUnifiedTopology: true,
        }
        );
        
    • replace above mongoose_link with your own. To get your link
      • Go to your Cluster dashboard in Mongo Atlas cluster.PNG
      • Click Connect > Connect Your Application and then copy your link and replace Password and dbname with your password and database name

    Succesfully connected database to application.

    • Now, we have to design our database model schema
      • If you remember we decided to use shorturl as primarykey
        const urlDbSchema = mongoose.Schema({
        _shortUrl: {
        type: String,
        require: true,
        },
        fullUrl: {
        type: String,
        require: true,
        },
        count: { type: Number, default: 0 },
        });
        
        • connect this model to DB so that we can use
     const urlsDb = mongoose.model("urls", urlDbSchema);
  • Now, our database is ready to operate.So, let us complete our post request processing with database

    app.post("/process", async (request, response) => {
     const userReqString = request.body.shortUrl;
     if (userReqString) {
     // user requested some string
    
     // checking if requested string is not taken already
    /f (await urlsDb.where({ _shortUrl: userReqString }).countDocuments > 0) {
      // if already exists redirecting to home page
      response.redirect("/");
       } 
     else {
     // requested string available
    
     // create new entry to insert to DB
     const temp = new urlsDb({
       fullUrl: request.body.fullUrl,
       _shortUrl: request.body.shortUrl,
     });
     urlsDb.insertMany(temp, (error) => {
       if (error) {
         //{ error: "Oops..! Backend Error" },
         response.redirect("/");
       } else {
         // success
         response.redirect("/");
           }
         });
       }
     } else {
      // user not requested any string 
      // assign a random string
       const temp = new urlsDb({ fullUrl: request.body.fullUrl, _shortUrl: 
    getValidId() });
     urlsDb.insertMany(temp, (error) => {
       if (error) {
         //{ error: "Oops..! Backend Error" },
           } else {
         // success
         response.redirect("/");
           }
         });
       }
         });
    
    • getValidId function generates a random string that is not present yet in the database
 ```js    
    // getValidId()
   function getValidId() {
  var randomId = getRandomId();
  while (urlsDb.where({ _shortUrl: randomId }).countDocuments > 0) {
    // console.error("still in while");
    randomId = getRandomId;
  }
  // console.log("random " + randomId);
  return randomId;
}

function getRandomId() {
  allowedChars =
  "_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  var randomStr = "";
  for (var i = 0; i < 13; i++) {
    randomStr += allowedChars[Math.floor(Math.random() * 
    allowedChars.length)];
    }

     return randomStr;
    }

  ```

We almost completed our app The one thing left behind is to handle shorturl and redirect it to original one.

  • When user requested some short url we will get a get request for that particular url
  • But scripting function to handle every url's get request is impossible.So we have an option to generalize this

app.get("/:keyword",callback) - handles get req for all urls in the form website.com/abcd.. and

this path string ( abcd here ) can be obtained from request.params.keyword

app.get("/:shorturl", async (request, response) => {
  const shorturl = request.params.shorturl;
  await urlsDb
    .findOne((error, result) => {
      if (error) {
       // database error
        response.send(error);
      } else {
        if (result) {
        // redirect to original url (Http Status code-301)
        response.redirect(result.fullUrl);
        }
      }
    })
    .where({ _shortUrl: shorturl });
});

That's it.. Congro ๐ŸŽ‰ we build our application ๐Ÿฅณ

The major step is Deploying.I want to deplot this for free as this is not for commercial purpose

  • I decided to deploy to heroku also I didnot find any better free alternatives to deploy Node JS applications

  • Head over to heroku Node JS guide

  • Follow the steps until you deploy the app Your app is on Internet now ๐Ÿฅณ๐Ÿฅณ But some people ( like me ) want to have this on custom domain (like mine minii.ml )
  • First register required domain name from any domain registrar ( I got mine from freenom . It offers free domain for 1 year so... )
  • Then go to heroku dashboard and select your app
  • Go to settings & scrolldown to Domains section
  • Click Add New Domain and enter domain name
  • Enter given DNS target to your domain or DNS Manager( I prefer to use cloudfare as CNAME record

cname.PNG

If your choose to add this to subdomain like subdomain.domain.com place subdomain as domain name for root domains like domain.com place @ in domain name . and place DNS target given in heroku here in target.

After some time your app will be active on your domain.. You succesfully created your own url shortener for free on domain of your wish ๐Ÿฅณ๐Ÿฅณ๐ŸŽ‰๐ŸŽ‰ If you like this Share the post Like this post and comment to get next post on How to add some additional features to this app like displaying error , user's shortened links in a table etc

Did you find this article valuable?

Support Rajasekhar Guptha by becoming a sponsor. Any amount is appreciated!