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 runtime environment
- MySQL as a database which also be obtained by using XAMPP or similar packages
- express application framework
- ejs to generate HTML template views
- shortid to generate unique and short ids for URLs
- 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:
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:
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:
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. Please feel free to share your thoughts rerarding it.
Happy Coding πππππ