Create a URL shortener using NodeJS & MongoDB.
Tags: tool, node, javascript
Published: 2021-07-31 17:06:09

In this post i will show you how to build a simple url shortener service like [bit.ly](https://bitly.com/) below is the final product.

![final-product](https://user-images.githubusercontent.com/30069626/126607472-e7040998-1026-4ae4-8ac3-000659a374ad.png)


## Getting Started

This is the list of npm packages we need to use to make this url shortener in this tutorial:

* [Express](https://expressjs.com/)
  A web application framework for the Nodejs applications.

* [mongoose](https://mongoosejs.com/)
  Used to model the objects for Nodejs for the Mongodatabase.

* [shortid](https://www.npmjs.com/package/shortid)
  Unique short id generator package for nodejs.

* [cors](https://www.npmjs.com/package/cors)
  CORS is a node.js package for providing a Connect/Express 
  middleware that can be used to enable CORS with various 
  options.
* [valid-url](https://www.npmjs.com/package/valid-url)
  valid-url is used to check the is the entered url is an valid url or not.


## Tutorial

Before starting the tutorial you must have on mongodb cloud account.because i'm not showing here how to create a mongodb cloud account You just visit[Mongodb](https://www.mongodb.com).

#Let's Begin🚀

Make a project directory to anywhere where you easily excess it anytime.

```
$mkdir url_shortner
$cd url_shortner
```

Intialize a node project by npm

```
$npm init -y
```

### Install required packages

```
$npm i express mongoose cors shortid  valid-url
```

### Create a index.js file 

```
const express = require('express');
const mongoose = require('mongoose');
const shortid = require('shortid');
const cors = require('cors');
const URL = require('./models/URL');
const bp = require('body-parser');
var validUrl = require('valid-url');



const app = express();

/*SET APP VIEW ENGINE TO EJS*/
app.set('view engine','ejs')
app.use(cors());
//set app static file directory to public
app.use(express.static('public'))
app.use(bp.json())
app.use(express.urlencoded({extended: true}));




    mongoose.connect('<Mongodb cloud url>/URLSHORTNER',{useNewUrlParser:true,useUnifiedTopology:true},()=>{
        console.log("Database connected!");
    });

// Database connection



// Routes

app.get('/',async (req,res) => {
    res.render('index',{title:'URLY-url shortner'});
})

app.get('/:uid',async (req,res) => {
    const url = await URL.findOne({shorten_id:req.params.uid});
    if(!url){
        return res.status(500).json({error:"Invalid Shorten URL!"});
    }
    res.redirect(url.url);
})
app.post('/',async (req,res) => {
    const {url} = req.body;
/// first validate the url
const isValid = validUrl.isUri(url)
console.log(isValid);
if(!isValid) {
    return res.status(400).json({error: 'Invalid URL'});
}
//check is there is same url present in database if yes give the shorten id of the url
   const isExists = await URL.findOne({url:url});
   if(!isExists) {
       const newURL = new URL({
           url,
           shorten_id:shortid.generate()
       })
       const result = await newURL.save();
       return res.send({shorten_url:`https://easyurly.herokuapp.com/${result.shorten_id}`});
   }
   
})


const port = process.env.PORT || 3000;

app.listen(port,()=>{
    
   
        console.log(`Server listen at ${port} & DB is also connected!`);
    
})
```

### Make a static public & views directory in root directory of the project

```
$mkdir public views

```
<p>create a css file inside public folder for our view file.</p>

<p> also create a views directory and create a index.ejs file inside it which renders an webpage on index route of our node application</p>

#### /views/index.ejs

```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/style.css">
    <title><%= title %> </title>
</head>
<body>
    <nav>
        <div class="contaier__nav">
            <div class="brand__name">
                <strong>URLY</strong>
            </div>
            <div class="links">
                <a href="https://dev.to/ammyy9908" class="btn-about" title="Feature not supported">About Devloper</a>
            </div>
        </div>
    </nav>
    <div class="container">
        <div class="container__left">
            <h1>Map Your Long Urls with URLY</h1>
            <p>A URL shortener/mapper built with NodeJS</p>
            <div class="input__div">
                <form><input type="text" name="url" placeholder="Shorten Your URL" autocomplete="off"><input type="submit" value="Shorten URL"></form>
                
            </div>
            <!-- <a href="#/" class="btn btn-primary">Explore</a> -->
        </div>
    </div>
    <div class="output">

        <a href="" class="shorten__url">Demo</a>
    </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.0/axios.min.js"></script>
    <script>
        const form = document.querySelector('form');
        form.addEventListener('submit', async (e)=>{
            e.preventDefault();
            const url =form.url.value;
            if(url){
               try {
                const r = await axios.post('http://localhost:5000/',{url:url});
            if (r.status === 200) {
                document.querySelector('.output').style.display = 'flex';
                
                   document.querySelector('.output').innerHTML = `<p href="#" class="shorten__url">${r.data.shorten_url}</p>`;
                   
                   form.reset();
            }
        } catch (e) {
            if (e.response && e.response.data) {
                alert(e.response.data.error);
                const error = e.response.data.error;
                showError(error);
                
            }
        }
            }
        })
        // add event on output to copy the url
        document.querySelector('.output').addEventListener('click', function (e){
            navigator.clipboard.writeText(e.target.textContent).then(()=>{
                e.target.innerHTML = '<p>URI COPIED!</p>';
                setTimeout(function () {
                    document.querySelector('.output').style.display = 'none';
                },3000);
            })
        })
        const showError = (err)=>{
            document.querySelector('.output').innerHTML = `<p>${err}</p>`;
        }
    </script>
</body>
</html>
```


### /public/style.css

```
:root{
    --primary-color:linear-gradient(to right, #92fe9d 0%, #00c9ff 100%);
}
body{
    margin: 0;
    padding: 0;
    background-color: #fafafa;
}

nav{
width: 100%;
height: 65px;
}
.contaier__nav{
    max-width: 1277px;
    display: flex;
    justify-content: space-between;
    margin: 0 auto;
    align-items: center;
    height: 100%;
}
.brand__name > strong{
    background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
    font-family: "ProximaNova ExtraBold","Helvetica Neue",Helvetica,Arial,sans-serif;
    font-size: 22px;
    margin-left: 10px;
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    transition:all 3s ease-in-out;
    background-size: 300%;
}
.links{
    display: flex;
    justify-content:space-around;
    flex: .3;
}
.links > a{
    text-decoration: none;
    font-family: "ProximaNova ExtraBold","Helvetica Neue",Helvetica,Arial,sans-serif;
    padding: 10px 32px;
    display: inline-flex;
    justify-content:flex-start;
    align-items:center;
    margin-right: 10px;
}
.btn-about{
    background: var(--primary-color);
    color: #fff;
    border-radius: 5px;
}
.disabled{
    cursor: not-allowed;
    background-color: #ccc;
}
.container{
    width: 100%;
    display: flex;
    align-items: center;
    margin-top: 35px;
    justify-content:center;
    margin-bottom: 35px;
    height: 75vh;

}
.container > div{
    max-width: 95%;
    padding-bottom: 35px;
}
.container__left{
    flex: 1;
    display:flex;
    flex-direction: column;
    justify-content:space-evenly;
    align-items: center;
}

.container__left > h1{
    font-family: "ProximaNova ExtraBold","Helvetica Neue",Helvetica,Arial,sans-serif;
    font-size: 55px;
    color: rgb(245, 14, 64);
    background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
    -webkit-background-clip: text;
    font-weight: 900;
    -webkit-text-fill-color: transparent;
    animation: gradients 10s infinite linear;
    background-size: 300%;
}
@keyframes gradients {
    0%{
       background-position: 0 50%;

    }
    50%{
        background-position: 100% 50%;
    }
    100%{
        background-position: 0 50%;

    }
}
.container__left > p{
    color: #56575b;
    font-weight: 400;
    font-size: 24px;
    font-family: "ProximaNova Regular","Helvetica Neue",Helvetica,Arial,sans-serif;
    margin-top: 15px;
}
.btn{
    font-size: 20px;
    padding: 18px 30px;
    background: var(--primary-color);
    border-radius: 6px;
    font-size: inherit;
    color: #fff;
    text-decoration: none;
    outline: none !important;
    font-family: "ProximaNova Regular","Helvetica Neue",Helvetica,Arial,sans-serif;
}
.input__div{
    width: 65%;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content:center;
    margin-top: 10px;
}
.input__div > form{
    flex: 1;
    display: flex;
    justify-content:space-evenly;
    align-items: center;
    width: 100%;
}
.input__div > form > input[type=text]{
flex: .5;
height: 45px;
padding-left: 10px;
border: 2px solid #84fab0;
outline: none;
border-radius: 4px;
}
.input__div > form > input[type=submit]{
flex:.4;
height: 50px;
padding-left: 10px;
background:linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
color: #fff;
border: 0;
outline: none;
border-radius: 5px;
text-transform: uppercase;
font-weight: 700;
transition:all 3s ease-in-out;
}
.input__div > form > input[type=submit]:hover{
  
    background:linear-gradient(60deg, #8fd3f4 0%, #84fab0 100%);
    
    
    }

.shorten__url{
    font-size: 12px;
    font-family: "ProximaNova Regular","Helvetica Neue",Helvetica,Arial,sans-serif;
    color: #fff;
    text-decoration: none;
}
.output{
    width: 100%;
    display: none;
    background-color: #262626;
    margin: 0;
    height: 45px;
    justify-content:center;
    align-items: center;
    transition: all .5s ease-in-out;
    position: fixed;
    bottom:0;
    cursor: pointer;
    
}
.output > p{
    font-size: 12px;
    font-family: "ProximaNova Regular","Helvetica Neue",Helvetica,Arial,sans-serif;
    color: #fff;
    text-decoration: none;
}

@media (max-width:768px){
    .links{
        display: none;
    }
```


## Create a Mongoose Model 
<p>Using Schema class of mongoose package .to do this create a file called URL.js inside a model directory and create a schema using Schema class and pass to model function by giving collection name & schema </p>


### /models/URL.js

```
const {model,Schema} = require('mongoose');

const URLSchema = new Schema({
    url:{
        type: 'string',
        required: true,
    },
    shorten_id:{
        type: 'string',
        required: true,
    }
});


module.exports = model('url',URLSchema);
```

###😇 Thanks for Reading....


Clone [Github Repo](https://github.com/Ammyy9908/url-shortener)