Developing a simple URL shortener with node, express, ejs and mysql
Tags: node, mysql, webdev, beginners
Published: 2021-12-17 00:40:33

A URL shortener is a pretty simple system that shortens longer URLs. On hitting the short URL, a user is automatically redirected to the actual URL. The main advantage of this is a user can share a short form of a very long URL. Today I would like to develop a simple URL shortener with node, express, ejs and mysql. 

###Features
Our web app will have the following features:
- Shorten lonnger URLs
- Redirect to main URL upon clicking the shorter one
- Copy the shorter URL to use anywhere
- Show the number of time a particular URL has been shorten

###Project Setup
We will need the followings for this project:
- [Node](https://nodejs.org/) runtime environment
- [MySQL](https://www.mysql.com/) as a database which also be obtained by using [XAMPP](https://www.apachefriends.org/) or similar packages
- [express](https://expressjs.com/) application framework
- [ejs](https://www.npmjs.com/package/ejs) to generate HTML template views
- [shortid](https://www.npmjs.com/package/shortid) to generate unique and short ids for URLs
- [nodemon](https://www.npmjs.com/package/nodemon) as a watcher to obtain auto reloading the project on each save

###Project Description
At first lets create a folder named `url-shortener` in our local machine and go to that folder. Now its time to create the `package.json` file and install necessary packages. Following commands will do so:
```
npm init -y
npm i express ejs mysql shortid
npm i --save-dev nodemon
```
We also need to update the script property with `"dev": "nodemon index.js"` which means on running `npm run dev` nodemon will run our entry file. So our `package.json` file will look like:
```
{
  "name": "url-shortener",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ejs": "^3.1.6",
    "express": "^4.17.1",
    "mysql": "^2.18.1",
    "shortid": "^2.2.16"
  },
  "devDependencies": {
    "nodemon": "^2.0.15"
  }
}
```
Now lets create the `index.js` file in our root directory along with two directories named `public` and `views` to store assets and ejs files respectively.

Lets describe the `index.js` file gradually. At first we import all the packages and start the express server.
```
const express = require("express");
const shortid = require("shortid");
const mysql = require("mysql");
const app = express();

app.listen(3000);
```
Now if we run the `npm run dev` command then in **http://localhost:3000/** of our browser express will run but we need to specify routes. Before that we specify the view engine and static file path.
```
app.set("view engine", "ejs");
app.use(express.static(__dirname + "/public"));
app.use(express.urlencoded({ extended: false }));
```
Now we define our home route like this:
```
app.get("/", (req, res) => {
    res.render("home.ejs");
});
```
Here it says whenever a request is created to the root path, it will show the home template file as the response. So inside the `views` directory we create the `home.ejs` file and write the following:
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>URL Shortener</title>
    <link rel="stylesheet" type="text/css" href="/styles/home.css" />
</head>
<body>
    <div class="container">
        <h2>URL Shortener</h2>
        <p>Convert long URL to shorter one with a single click. Its easy, simple and absolutely free!</p>
        <form action="/shortUrl" method="post">
            <input type="url" placeholder="Enter the URL" name="fullUrl" required />
            <input type="submit" value="Convert" />
        </form>
    </div>
</body>
</html>
```
Here we have added a css file named `home.css` which should remain in a folder named `styles` of the `public` directory. This means we have to create the `styles` directory inside `public` directory and create `home.css` inside it. Then we write the follwing css code:
```
.container {
    width: 50%;
    margin: auto;
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 10px;
}

h2 {
    margin: 0;
}

p {
    max-width: 350px;
}

input[type="url"] {
    height: 28px;
    width: 250px;
    padding-left: 8px;
    border-radius: 4px;
    border: 1px solid #000;
}

input[type="submit"] {
    padding: 10px 20px;
    color: #fff;
    background-color: #349ded;
    border: none;
    border-radius: 4px;
    margin-left: 5px;
}

input[type="submit"]:hover {
    cursor: pointer;
    opacity: 0.85;
}

.span-link {
    padding: 10px 20px;
    border-radius: 4px;
    background-color: #349ded;
    color: #fff;
}

.result-container {
    background-color: #dddcdc;
    padding: 10px;
    min-width: 200px;
    display: flex;
    justify-content: space-around;
}

a {
    text-decoration: none;
}

.copy-span:hover {
    cursor: pointer;
    opacity: 0.75;
}
```
Now upon saving our code our browser should look like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbpwkzo7vgrp7n61xe88.PNG)
Now if we add a URL in the input section and click `Convert`, it will not work because we have not defined our route `/shortUrl` for `<form action="/shortUrl" method="post">`. In order to create this route we need to create our database and table at first. I have used `XAMPP` to do so. After running `Apache` and `MySQL` processes of XAMPP control panel we go to **http://localhost/phpmyadmin/** and create a database named `url_shortener`. Then we create a table named `url` which has the following structure:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yy2xcapopju53xbty18z.PNG)
We can see that the table has four properties namely an auto increment id, fullUrl, shortUrl and counts which stores the number of times a particular URL gets shortened. Now its time to connect our database. We add the followings in our index file:
```
const db = mysql.createConnection({
    host: "localhost",
    user: "root",
    password: "",
    database: "url_shortener"
});

db.connect(err => {
    if(err) {
        console.log("Error connecting to DB");
        return;
    }
    console.log("Connceted to DB");
});
```
After this its time to create our `/shorturl` post route. Here our logic is pretty much simple. Our request body contains a parameter named `fullUrl` that is given as an input by the user. At first we query to the db with that parameter whether an entry exists. If not then we create a new entry with that fullUrl, its generated shortid and counts 1. Then we pass `shortUrl` and `counts` as object to a new view named `result.ejs`. If the entry exists then we simply increase its counts by 1 and pass `shortUrl` and `counts` as object to the view. Lets see our route now:
```
app.post("/shorturl", (req, res) => {
    const fullUrl = req.body.fullUrl;
    if (!fullUrl) {
        return res.sendStatus(404);
    }
    db.query('SELECT * FROM `url` WHERE `fullUrl` = ?', [fullUrl], (error, results) => {
        if (error) {
            console.log("we got error");
            return;
        }

        if (results.length === 0) {
            const short = shortid.generate();
            const url = { fullUrl: req.body.fullUrl, shortUrl: short, counts: 1 };
            db.query('INSERT INTO `url` SET ?', url, (err, res) => {
                if (err) {
                    console.log("Error creating table");
                    return;
                }
            });
            res.render("result.ejs", { shortUrl: short, times: 1 });
        } else {
            const _short = results[0].shortUrl;
            const _counts = results[0].counts;
            db.query('UPDATE `url` SET `counts` = ? WHERE `shortUrl` = ?', [_counts + 1, _short], (err, res) => {
                if (err) {
                    console.log("Error updating table");
                    return;
                }
            });
            res.render("result.ejs", { shortUrl: _short, times: _counts + 1 });
        }
    });
});
```
In the same time we create `result.ejs` file inside `views` directory and add following:
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>URL Shortener</title>
    <link rel="stylesheet" type="text/css" href="/styles/home.css" />
</head>
<body>
    <div class="container">
        <h2>URL Shortener</h2>
        <p>Your shortened URL is</p>
        <div class="result-container">
            <span><a id="short-url" href="<%= `/${shortUrl}` %>" target="_blank"><%= shortUrl %></a></span>
            <span onclick="copyUrl()" class="copy-span" id="copy-action">Copy</span>
        </div>
        <p>It has been converted <%= times %> times</p>
        <br />
        <a href="/"><span class="span-link">Try Another</span></a>
    </div>
    <script>
        const copyUrl = () => {
            const copyTextarea = document.getElementById("short-url").href;
            navigator.clipboard.writeText(copyTextarea);
            document.getElementById("copy-action").innerHTML = "Copied";
        };
    </script>
</body>
</html>
```
Now upon saving our files lets copy **https://www.youtube.com/watch?v=dwKSRsmpYjc&ab_channel=INSIDE**, paste it to our input field and click `Convert`. We see something like this:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m34nwwdfkucxf93bhmll.PNG)
Here by clicking the `Copy` field we can copy our short URL and clicking the short URL we can go to a new tab but unfortunately it will not redirect to the actual URL because we have not defined our corresponding route yet. So lets define it:
```
app.get("/:shortUrl", (req, res) => {
    db.query('SELECT * FROM `url` WHERE `shortUrl` = ?', [req.params.shortUrl], (error, results) => {
        if (error) {
            return res.sendStatus(404);
        }

        if (results.length === 0) {
            res.render("error.ejs");
        } else {
            res.redirect(results[0].fullUrl);
        }
    });
});
```
Here we are sending a dynamic parameter with our route path and looking for an entry with that short URL in our database. If an entry exists then we simply redirect to the `fullUrl` of it. Otherwise we render an `error.ejs` page that shows an error page and asks to visit the home page. Its code is:
```
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Error Page</title>
    <link rel="stylesheet" type="text/css" href="/styles/home.css" />
</head>
<body>
    <div class="container">
        <h2>URL Shortener</h2>
        <p>The URL you entered does not exist!</p>
        <br />
        <a href="/"><span class="span-link">Visit Home Page</span></a>
    </div>
</body>
</html>
```
Thus we have developed a simple URL Shortener website very easily. The full code can be found [here](https://github.com/alim1496/url-shortener-express). Please feel free to share your thoughts rerarding it.

Happy coding 😀😀😀