Building a Short URL Project with Express, Mongoose, MongoDB, and Node.js

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

    • URL: localhost:8003/url

    • Headers:

      • Content-Type: application/json
    • Body:

        jsonCopy code{
          "url": "https://www.example.com"
        }
      

Response:

  • Status: 200 (OK)

  • Body:

      jsonCopy code{
        "id": "QXGc8Wp2"
      }
    

Test Steps:

  1. Send a POST request to the /url endpoint with a valid long URL.

  2. Verify that the response status is 200.

  3. 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

  • URL: localhost:8003/url

  • Headers:

    • Content-Type: application/json
  • Body:

      jsonCopy code{
        "url": ""
      }
    

Response:

  • Status: 400 (Bad Request)

  • Body:

      jsonCopy code{
        "error": "URL is required"
      }
    

Test Steps:

  1. Send a POST request to the /url endpoint with an empty URL in the request body.

  2. Verify that the response status is 400.

  3. 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:

  1. Send a GET request to the /url/:shortId endpoint with the shortId parameter set to a valid short URL ID.

  2. Verify that the response status is 200.

  3. 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

  • LinkedIn: Click here

  • Github: Click here

  • Twitter: Click here

If you find my blog helpful you all can always support me by sponsoring me!

Did you find this article valuable?

Support Nikhil's Blog by becoming a sponsor. Any amount is appreciated!