Build and Deploy URL Shortener to custom domain from scratch -  Node JS
Tags: javascript, webdev, showdev, productivity
Published: 2020-10-19 12:25:38

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

***Structure:***

![minii_stack.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1603019184959/ae4rZYvvD.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)

```

2.  Let's design our site next..
I made this simple design using Bootstrap
![minii_proto.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1603018320776/nG5e92-8k.png)
 
  + As we are going to use **ejs** 
  + `npm install ejs` and  `npm install express` in terminal ( documentation  [here](https://ejs.co/)  )
  + 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
        
```js
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

```js
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

```js

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 
  ```js
       (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,

```js
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](https://en.wikipedia.org/wiki/Primary_key) 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 )

    ```html
          <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

    ```js
       app.post("/process", (request, response) => {

      }
   ```
  + user filled values can be obtained from **request** arg like

   ```js
      request.body.name  - name : given for input fields

     // In our case 
      request.body.fullUrl    
      request.body.shortUrl
    
   ```
   + We can check this 
   ```js
         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](https://docs.atlas.mongodb.com/getting-started/)  )
   + Install mongoose 
       +  `npm install mongoose` 
       + setup mongoose in app
        ```js

           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](https://cdn.hashnode.com/res/hashnode/image/upload/v1603028800901/bElYwOWZD.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
         ```js
        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

   ```js
    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](heroku.com) also I didnot find any better free alternatives to deploy Node JS  applications

1. Head over to  [heroku Node JS guide](https://devcenter.heroku.com/articles/getting-started-with-nodejs#set-up)
2. 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/ ) 
3. First register required domain name from any domain registrar ( I got mine from freenom . It offers free domain for 1 year so... )
3. Then go to  [heroku dashboard](https://dashboard.heroku.com/) and select your app
4. Go to settings & scrolldown to **Domains** section
5. Click **Add New Domain** and enter domain name 
6. Enter given DNS target to your domain or DNS Manager( I prefer to use  [cloudfare](https://www.cloudflare.com/ as DNS Manager)  as CNAME record

![cname.PNG](https://cdn.hashnode.com/res/hashnode/image/upload/v1603032876481/DZ616W87z.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**