DEV Community

Cover image for Create a URL shortener using NodeJS & MongoDB.
sumit kumar bighaniya
sumit kumar bighaniya

Posted on • Updated on

Create a URL shortener using NodeJS & MongoDB.

In this post i will show you how to build a simple url shortener service like bit.ly below is the final product.

final-product

Getting Started

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

  • Express
    A web application framework for the Nodejs applications.

  • mongoose
    Used to model the objects for Nodejs for the Mongodatabase.

  • shortid
    Unique short id generator package for nodejs.

  • 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
    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 visitMongodb.

Let's BeginπŸš€

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

$mkdir url_shortner
$cd url_shortner
Enter fullscreen mode Exit fullscreen mode

Intialize a node project by npm

$npm init -y
Enter fullscreen mode Exit fullscreen mode

Install required packages

$npm i express mongoose cors shortid  valid-url
Enter fullscreen mode Exit fullscreen mode

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!`);

})
Enter fullscreen mode Exit fullscreen mode

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

$mkdir public views

Enter fullscreen mode Exit fullscreen mode

create a css file inside public folder for our view file.

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

/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>
Enter fullscreen mode Exit fullscreen mode

/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;
    }
Enter fullscreen mode Exit fullscreen mode

Create a Mongoose Model

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

/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);
Enter fullscreen mode Exit fullscreen mode

πŸ˜‡ Thanks for Reading....

Clone Github Repo

Discussion (0)