23
loading...
This website collects cookies to deliver better user experience
GET
request to the short URL, the URL will be searched within the database, and the user will be redirected to the corresponding original URL. Sound complex? Don't worry, we'll cover everything you need to know.npm init
in the project directory to initialize. Once the project is initialized, we are going to install the required dependencies. The dependencies that we need are:.env
to process.env
npm i dotenv express mongoose shortid
npm i -D nodemon
app.js
file using Express. To set up an Express server, we need to import the Express package into the app.js
file. Once the package is imported, initialize and store it into a variable called app
.listen
function to create the server. Here's an example.const Express = require('Express');
const app = Express();
// Server Setup
const PORT = 3333;
app.listen(PORT, () => {
console.log(`Server is running at PORT ${PORT}`);
});
3333
to run the server. The listen
method in Express starts a UNIX socket and listens for a connection in a given port..env
file inside the config
folder to store the MongoDB SRV URI and the base URL. The base URL will be your local host server location for now. Here's my .env
file code:MONGO_URI=mongodb+srv://nemo:[email protected]/myFirstDatabase?retryWrites=true&w=majority
BASE=http://localhost:3333
<password>
field in the MongoDB URI with your database password.db.js
file, which is inside the config
folder.const mongoose = require('mongoose');
require('dotenv').config({ path: './.env' });
path
object key is passed inside the dotenv config because the .env
file is not located in the root directory. We are passing the location of the .env
file through this.connectDB
within a file called db.js
, inside the config
folder. I'll use async/await for this article.const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log('Database Connected');
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
module.exports = connectDB;
try
block, we wait for Mongoose to connect with the given MongoDB URI. The first parameter in the mongoose.connect
method is the MongoDB SRV URI. Notice that the two key-value pairs are passed in the second parameter to remove the console warnings. Let's understand what the two key-value parameters mean.useNewUrlParser: true
: the underlying MongoDB driver has deprecated the current connection string parser. This is why it has added a new flag. If the connection encounters any issue with the new string parser, it can fall back to the old oneuseUnifiedTopology: true
: this is set to false
by default. Here, it is set to true
so that the MongoDB driver's new connection management engine can be usedcatch
statement, we will console log the error and exit with process.exit(1)
. Finally, we export the function with module.exports
.db.js
file into the app.js
file with const connectDB = require('./config/db');
and call the connectDB
function with connectDB()
.Url.js
inside a models
folder. Import Mongoose here, then use the mongoose.Schema
constructor to create the schema.const mongoose = require('mongoose');
const UrlSchema = new mongoose.Schema({
urlId: {
type: String,
required: true,
},
origUrl: {
type: String,
required: true,
},
shortUrl: {
type: String,
required: true,
},
clicks: {
type: Number,
required: true,
default: 0,
},
date: {
type: String,
default: Date.now,
},
});
module.exports = mongoose.model('Url', UrlSchema);
module.exports = mongoose.model('Url', UrlSchema);
. The first parameter inside mongoose.model
is the singular form of the data that is to be stored, and the second parameter is the schema itself.routes
in the root directory and a file named urls.js
inside of it. We are going to use the Express router here. First, import all of the necessary packages, like so.const Express = require('express');
const router = Express.Router();
const shortid = require('shortid');
const Url = require('../models/Url');
const utils = require('../utils/utils');
require('dotenv').config({ path: '../config/.env' });
utils.js
file inside the utils
folder consists of a function that checks if a passed URL is valid or not. Here's the code for the utils.js
file.function validateUrl(value) {
return /^(?:(?:(?:https?|ftp):)?\\/\\/)(?:\\S+(?::\\S*)?@)?(?:(?!(?:10|127)(?:\\.\\d{1,3}){3})(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))(?::\\d{2,5})?(?:[/?#]\\S*)?$/i.test(
value
);
}
module.exports = { validateUrl };
urls.js
file to generate and post the details to the database.const Express = require('express');
const router = Express.Router();
const shortid = require('shortid');
const Url = require('../models/Url');
const utils = require('../utils/utils');
require('dotenv').config({ path: '../config/.env' });
// Short URL Generator
router.post('/short', async (req, res) => {
const { origUrl } = req.body;
const base = process.env.BASE;
const urlId = shortid.generate();
if (utils.validateUrl(origUrl)) {
try {
let url = await Url.findOne({ origUrl });
if (url) {
res.json(url);
} else {
const shortUrl = `${base}/${urlId}`;
url = new Url({
origUrl,
shortUrl,
urlId,
date: new Date(),
});
await url.save();
res.json(url);
}
} catch (err) {
console.log(err);
res.status(500).json('Server Error');
}
} else {
res.status(400).json('Invalid Original Url');
}
});
module.exports = router;
const { origUrl } = req.body;
will extract the origUrl
value from the HTTP request body. Then we store the base URL into a variable. const urlId = shortid.generate();
is generating and storing a short ID to a variable.utils
directory. For valid URLs, we move into the try
block.Url.findOne({ origUrl });
Mongoose method. If found, we return the data in JSON format; otherwise, we create a short URL combining the base URL and the short ID.url.save();
method. Once saved, we return the response in JSON format.try
block are handled in the catch
block, and invalid URLs that return false
in our validateUrl
function send back a message that the URL is invalid. Finally, we export the router.app.js
file and add these two lines to use body-parser:// Body Parser
app.use(Express.urlencoded({ extended: true }));
app.use(Express.json());
app.use('/api', require('./routes/urls'));
/api
endpoint, our complete endpoint becomes http://localhost:3333/api/short
. Here's an example.index.js
inside the routes
folder to handle the redirection process. In this file, import the necessary dependencies.const Express = require('express');
const router = Express.Router();
const Url = require('../models/Url');
router.get('/:urlId', async (req, res) => {
try {
const url = await Url.findOne({ urlId: req.params.urlId });
if (url) {
url.clicks++;
url.save();
return res.redirect(url.origUrl);
} else res.status(404).json('Not found');
} catch (err) {
console.log(err);
res.status(500).json('Server Error');
}
});
module.exports = router;
GET
request is getting the URL ID with the help of :urlId
. Then, inside the try
block, we find the URL using the Url.findOne
method, similar to what we did in the urls.js
route.return res.redirect(url.origUrl);
.catch
block. We console log the error and send a JSON message of "Server Error". Finally, we export the router.app.js
file, and our URL shortener is ready to use. After importing it, our final app.js
file will look like this:const Express = require('Express');
const app = Express();
const connectDB = require('./config/db');
require('dotenv').config({ path: './config/.env' });
connectDB();
// Body Parser
app.use(Express.urlencoded({ extended: true }));
app.use(Express.json());
app.use('/', require('./routes/index'));
app.use('/api', require('./routes/urls'));
// Server Setup
const PORT = 3333;
app.listen(PORT, () => {
console.log(`Server is running at PORT ${PORT}`);
});