Building a URL Shortener App with Node.js and MongoDB

Manthan Ankolekar - Jul 23 - - Dev Community

Creating a URL shortener service is a great way to dive into full-stack development with Node.js and MongoDB. In this blog, we'll walk through the process of building a URL shortener application, which allows users to convert long URLs into shortened versions and track their usage.

Prerequisites

To follow this tutorial, you should have:

  • Node.js installed
  • Basic knowledge of JavaScript and Express.js
  • A MongoDB database (you can use MongoDB Atlas for a cloud solution)

Project Setup

Step 1: Initialize the Project

First, create a new directory for your project and initialize it with npm:

mkdir url-shortener-app
cd url-shortener-app
npm init -y
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Dependencies

Next, install the necessary dependencies:

npm install express mongoose cors dotenv
npm install --save-dev nodemon
Enter fullscreen mode Exit fullscreen mode

Step 3: Project Structure

Create the following folder structure:

url-shortener-app/
├── controllers/
│   └── urlController.js
├── models/
│   └── urlModel.js
├── routes/
│   └── urlRoutes.js
├── .env
├── index.js
├── package.json
Enter fullscreen mode Exit fullscreen mode

Building the Backend

Step 4: Set Up Express Server

In the index.js file, set up the Express server and connect to MongoDB:

const express = require('express');
const mongoose = require('mongoose');
const urlRoutes = require('./routes/urlRoutes');
const cors = require('cors');

const app = express();
app.use(express.json());

require("dotenv").config();

const dbUser = process.env.MONGODB_USER;
const dbPassword = process.env.MONGODB_PASSWORD;

// Connect to MongoDB
mongoose
    .connect(
        `mongodb+srv://${dbUser}:${dbPassword}@cluster0.re3ha3x.mongodb.net/url-shortener-app`,
        { useNewUrlParser: true, useUnifiedTopology: true }
    )
    .then(() => {
        console.log("Connected to MongoDB database!");
    })
    .catch((error) => {
        console.error("Connection failed!", error);
    });

app.use(cors({
    origin: "*",
}));

app.get('/', (req, res) => {
    res.send('Welcome to URL Shortener API');
});

app.use('/api', urlRoutes);

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Create a .env file in the root directory to store environment variables:

MONGODB_USER=yourMongoDBUsername
MONGODB_PASSWORD=yourMongoDBPassword
Enter fullscreen mode Exit fullscreen mode

Step 5: Define the URL Model

In the models/urlModel.js file, define the schema for URLs:

const mongoose = require('mongoose');

const urlSchema = new mongoose.Schema({
    originalUrl: { type: String, required: true },
    shortUrl: { type: String, required: true, unique: true },
    clicks: { type: Number, default: 0 },
    expirationDate: { type: Date },
    createdAt: { type: Date, default: Date.now },
});

module.exports = mongoose.model('Url', urlSchema);
Enter fullscreen mode Exit fullscreen mode

Step 6: Create Routes

In the routes/urlRoutes.js file, define the routes for the API:

const express = require('express');
const { createShortUrl, redirectUrl, getUrls, getDetails, deleteUrl } = require('../controllers/urlController');
const router = express.Router();

router.post('/shorten', createShortUrl);
router.get('/urls', getUrls);
router.get('/:shortUrl', redirectUrl);
router.get('/details/:shortUrl', getDetails);
router.delete('/delete/:shortUrl', deleteUrl);

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

Step 7: Implement Controllers

In the controllers/urlController.js file, implement the controller functions:

const Url = require('../models/urlModel');

function generateUniqueId(length) {
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let result = '';
    for (let i = 0; i < length; i++) {
        const randomIndex = Math.floor(Math.random() * characters.length);
        result += characters[randomIndex];
    }
    return result;
}

const createShortUrl = async (req, res) => {
    const { originalUrl } = req.body;
    const shortUrl = generateUniqueId(5);
    const urlRegex = new RegExp(/^(http|https):\/\/[^ "]+$/);
    if (!urlRegex.test(originalUrl))
        return res.status(400).json('Invalid URL');
    const url = await Url.findOne({ originalUrl });
    if (url) {
        res.json(url);
        return;
    }
    const expirationDate = new Date();
    expirationDate.setDate(expirationDate.getDate() + 7);
    const newUrl = new Url({ originalUrl, shortUrl, expirationDate });
    await newUrl.save();
    res.json(newUrl);
};

const redirectUrl = async (req, res) => {
    const { shortUrl } = req.params;
    const url = await Url.findOne({ shortUrl });
    if (!url || (url.expirationDate && url.expirationDate < new Date())) {
        res.status(404).json('URL expired or not found');
        return;
    }
    url.clicks++;
    await url.save();
    res.redirect(url.originalUrl);
};

const getUrls = async (req, res) => {
    try {
        const urls = await Url.find({}).sort({ _id: -1 });
        res.json(urls);
    } catch (error) {
        res.status(500).json({ message: 'Server Error' });
    }
};

const getDetails = async (req, res) => {
    try {
        const { shortUrl } = req.params;
        const url = await Url.findOne({ shortUrl });
        if (url) {
            res.json(url);
        } else {
            res.status(404).json('URL not found');
        }
    } catch (error) {
        res.status(500).json({ message: 'Server Error' });
    }
};

const deleteUrl = async (req, res) => {
    try {
        const { shortUrl } = req.params;
        await Url.findOneAndDelete({ shortUrl });
        res.json('URL deleted');
    } catch (error) {
        res.status(500).json({ message: 'Server Error' });
    }
};

module.exports = { createShortUrl, redirectUrl, getDetails, getUrls, deleteUrl };
Enter fullscreen mode Exit fullscreen mode

Running the Application

Start the server using the following command:

npm run dev
Enter fullscreen mode Exit fullscreen mode

This command will start your server with Nodemon, which will automatically restart the server when you make changes to your code.

Conclusion

In this blog, we've built a simple URL shortener application using Node.js and MongoDB. This application allows users to shorten URLs, track their usage, and manage them with expiration dates. This project is a great starting point for learning about full-stack development and can be expanded with additional features such as user authentication, custom URL aliases, and more.

Exploring the Code

Visit the GitHub repository to explore the code in detail.


Feel free to extend this application by adding features like user authentication, etc. Happy coding!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .