Building a Short URL Project with Express, Mongoose, MongoDB, and Node.js
Simplifying Links: Let's create a URL shortener
Introduction
Hey everyone welcome back, this is a step-by-step guide on building a Short URL project using Express, Mongoose, MongoDB, and Node.js! In this project, we'll create a URL shortening service that allows users to generate short, customized URLs for their long and complex web addresses. By following this project, you'll gain hands-on experience with essential technologies and concepts. You'll learn how to set up an Express server, define routes, handle requests and responses, and manage middleware. Additionally, you'll explore Mongoose, an elegant MongoDB object modeling tool, to define schemas, create models, and perform database operations. MongoDB, a popular NoSQL database, will be used to store the URLs and their corresponding analytics data.
Prerequisites
Before we dive into the code, ensure you have the following prerequisites:
Node.js installed on your machine
Basic knowledge of JavaScript
MongoDB installed locally or a remote MongoDB instance
Setting up the Project
To get started, create a new directory for your project and initialize a new Node.js project by running the following command:
$ mkdir short-url-project
$ cd short-url-project
$ npm init -y
Next, install the necessary dependencies: Express, Mongoose, and shortid.
$ npm install express mongoose shortid
With the project set up, let's start building our short URL service!
Connecting to MongoDB
In the connect.js
file, we will define a function to connect to our MongoDB database using Mongoose. Create a new file connect.js
in the project directory and add the following code:
// connect.js
const mongoose = require('mongoose');
async function connectToMongoDB(url) {
try {
await mongoose.connect(url);
console.log('MongoDB connected');
} catch (error) {
console.error('Failed to connect to MongoDB:', error);
}
}
module.exports = {
connectToMongoDB,
};
This function establishes a connection to the MongoDB database using the provided URL. It's good practice to handle any potential errors during the connection process.
Creating the URL Model
The next step is to define the data model for our URLs. Create a new file models/url.js
and add the following code:
// models/url.js
const mongoose = require('mongoose');
const urlSchema = new mongoose.Schema(
{
shortId: {
type: String,
required: true,
unique: true,
},
redirectURL: {
type: String,
required: true,
},
visitHistory: [{ timestamp: { type: Number } }],
},
{ timestamps: true }
);
const URL = mongoose.model('URL', urlSchema);
module.exports = URL;
Here, we define a schema for our URLs using Mongoose's Schema
class. The schema includes fields for the shortId
(the generated short URL identifier), redirectURL
(the original long URL to which the short URL will redirect), and visitHistory
(an array of visit timestamps). The timestamps
option automatically adds createdAt
and updatedAt
fields to the documents.
Creating the URL Routes
Now, let's create the routes responsible for generating short URLs and handling analytics. Create a new directory called routes
and add a file named url.js
with the following code:
// routes/url.js
const express = require('express');
const router = express.Router();
const { generateShortUrl, handlegetAnalytics } = require('../controllers/url');
router.post('/', generateShortUrl);
router.get('/:shortId', handlegetAnalytics);
module.exports = router;
In this file, we define two routes: one for generating short URLs and another for retrieving analytics data. These routes will be handled by the corresponding functions defined in the controllers/url.js
file, which we'll implement next.
Implementing the URL Controllers
Create a new file controllers/url.js
and add the following code:
// controllers/url.js
const shortid = require('shortid');
const URL = require('../models/url');
// Controller function to generate a short URL
async function generateShortUrl(req, res) {
const body = req.body;
// Check if the request body contains a URL
if (!body.url) {
return res.status(400).json({ error: 'URL is required' });
}
// Generate a unique short ID
const shortId = shortid.generate();
try {
// Create a new document in the MongoDB database
await URL.create({
shortId: shortId,
redirectURL: body.url,
visitHistory: [],
});
return res.json({ id: shortId });
} catch (error) {
console.error('Failed to generate short URL:', error);
return res.status(500).json({ error: 'Failed to generate short URL' });
}
}
// Controller function to handle retrieving analytics data
async function handlegetAnalytics(req, res) {
const shortId = req.params.shortId;
try {
// Find the corresponding document in the database
const result = await URL.findOne({ shortId });
return res.json({
totalClicks: result.visitHistory.length,
analytics: result.visitHistory,
});
} catch (error) {
console.error('Failed to retrieve analytics:', error);
return res.status(500).json({ error: 'Failed to retrieve analytics' });
}
}
module.exports = {
generateShortUrl,
handlegetAnalytics,
};
In the generateShortUrl
function, we first validate that the request body contains a url
field. If not, we respond with a 400 error indicating that the URL is required. We then generate a unique short ID using the shortid
package and create a new document in the MongoDB database with the provided URL and an empty visitHistory
array.
The handlegetAnalytics
function retrieves the analytics data for a given short URL. It finds the corresponding document in the database using the shortId
parameter and returns the total number of clicks and the visit history.
Setting up the Express Server
Finally, let's set up the Express server and connect the routes. In the index.js
file, add the following code:
// index.js
const express = require('express');
const { connectToMongoDB } = require('./connect');
const urlRoute = require('./routes/url');
const URL = require('./models/url');
const app = express();
const port = 8003;
// Connect to the MongoDB database
connectToMongoDB('mongodb://127.0.0.1:27017/short-url')
.then(() => {
console.log('MongoDB connected');
})
.catch((error) => {
console.error('Failed to connect to MongoDB:', error);
process.exit(1);
});
app.use(express.json());
// Mount the URL routes
app.use('/url', urlRoute);
app.get('/:shortId', async (req, res) => {
const shortId = req.params.shortId;
try {
// Find the corresponding document in the database and update the visit history
const entry = await URL.findOneAndUpdate(
{ shortId },
{ $push: { visitHistory: { timestamp: Date.now() } } }
);
// If the document is found, redirect the user to the original URL
if (!entry) {
return res.status(404).json({ error: 'Short URL not found' });
}
return res.redirect(entry.redirectURL);
} catch (error) {
console.error('Failed to retrieve short URL:', error);
return res.status(500).json({ error: 'Failed to retrieve short URL' });
}
});
app.listen(port, () => {
console.log(`Server started at port: ${port}`);
});
In this file, we first import the required modules and set up the server on port 8003. We then connect to the MongoDB database using the connectToMongoDB
function from connect.js
. If the connection is successful, the server starts listening for incoming requests.
We also set up a route for retrieving short URLs by their shortId
. Inside the route handler, we find the corresponding document in the database using findOneAndUpdate
and update the visitHistory
by pushing a new timestamp. If the document is found, we redirect the user to the original URL; otherwise, we respond with a 404 error.
API Tests Documentation
In this section, we will provide documentation for testing the APIs of the short URL project. The tests ensure that the endpoints are functioning as expected and handle different scenarios.
Test Scenario 1: Generate a Short URL
Endpoint: POST /url
Description: This endpoint generates a short URL for a given long URL.
Request:
Method: POST
Headers:
- Content-Type: application/json
Body:
jsonCopy code{ "url": "https://www.example.com" }
Response:
Status: 200 (OK)
Body:
jsonCopy code{ "id": "QXGc8Wp2" }
Test Steps:
Send a POST request to the
/url
endpoint with a valid long URL.Verify that the response status is 200.
Verify that the response body contains the generated short URL ID.
Test Scenario 2: Missing URL in Request
Endpoint: POST /url
Description: This test ensures that an error is returned when the URL is missing from the request body.
Request:
Method: POST
Headers:
- Content-Type: application/json
Body:
jsonCopy code{ "url": "" }
Response:
Status: 400 (Bad Request)
Body:
jsonCopy code{ "error": "URL is required" }
Test Steps:
Send a POST request to the
/url
endpoint with an empty URL in the request body.Verify that the response status is 400.
Verify that the response body contains the error message stating that the URL is required.
Test Scenario 3: Retrieve Short URL Analytics
Endpoint: GET /url/:shortId
Description: This endpoint retrieves the analytics data for a given short URL.
Request:
Response:
Status: 200 (OK)
Body:
jsonCopy code{ "totalClicks": 3, "analytics": [ { "timestamp": 1624222458721 }, { "timestamp": 1624222465203 }, { "timestamp": 1624222469432 } ] }
Test Steps:
Send a GET request to the
/url/:shortId
endpoint with theshortId
parameter set to a valid short URL ID.Verify that the response status is 200.
Verify that the response body contains the total number of clicks and an array of analytics data, including timestamps.
By following the provided API test documentation, you can execute the tests and ensure that the endpoints in your short URL project are functioning correctly.Adjust the code and test scenarios based on the testing tools you prefer to use.
Conclusion
Congratulations, we have successfully built a short URL project using Express, Mongoose, MongoDB, and Node.js. This project allows you to generate short URLs, retrieve analytics data, and redirect users to the original URLs.
This blog post covered the steps required to set up the project, connect to MongoDB, define the URL model, create routes, implement controllers, and set up the Express server. Feel free to customize and expand this project to meet your specific requirements.
You can find the complete code for this project in the main branch of the GitHub repository in the ejsbranch we have added the server-side rendering using ejs and build a very basic ui.
Thank you for reading, and happy coding!
Let's Connect
If you enjoyed this post and would like to stay updated on my work, feel free to connect with me on social media
If you find my blog helpful you all can always support me by sponsoring me!