URL Shortener Project Documentation


URL Shortener Project Documentation

Table of Contents

  1. Introduction

  2. Project Structure

  3. Installation Guide

  4. Usage Instructions

  5. API Documentation

  6. Code Snippets

  7. Contributing

  8. License


1. Introduction

The URL Shortener project is a web application that allows users to shorten long URLs into more manageable and shareable links. The application also provides analytics for the shortened URLs, such as the number of clicks and visit history.

Technologies Used

  • Node.js: JavaScript runtime environment.

  • Express.js: Web framework for Node.js.

  • MongoDB: NoSQL database for storing URL data.

  • EJS: Templating engine for rendering HTML.

  • Nanoid: Library for generating unique short IDs.


2. Project Structure

The project is organized as follows:

  • controllers/: Contains the logic for handling requests.

    • url.js: Handles URL shortening and analytics.
  • models/: Contains the data models.

    • url.js: Defines the schema for URL data.
  • public/: Static files like CSS, JavaScript, and images.

  • routes/: Defines the routes for the application.

    • staticRouter.js: Handles the home page route.

    • url.js: Handles URL-related routes.

  • views/: Contains the EJS templates for rendering HTML.

    • home.ejs: The main view for the home page.
  • .gitignore: Specifies files and directories to be ignored by Git.

  • connect.js: Handles the connection to the MongoDB database.

  • index.js: The main entry point of the application.

  • package.json: Lists project dependencies and scripts.

  • package-lock.json: Automatically generated file for dependencies.


3. Installation Guide

Prerequisites

  • Node.js and npm installed on your machine.

  • MongoDB installed and running locally.

Steps to Set Up the Project

  1. Clone the Repository

     git clone <repository-url>
     cd urlsortner
    
  2. Install Dependencies

     npm install
    
  3. Set Up MongoDB

  4. Run the Application

     node index.js
    
  5. Access the Application


4. Usage Instructions

Shortening a URL

  1. Visit the home page at http://localhost:3000.

  2. Enter the long URL in the input field and submit.

  3. The application will generate a shortened URL and display it on the page.

Accessing Analytics

  1. Use the shortened URL to access the analytics.

  2. Visit http://localhost:3000/url/analytics/<shortid> to view the number of clicks and visit history for the shortened URL.


5. API Documentation

Generate Short URL

  • Endpoint: POST /url

  • Request Body:

      {
        "url": "https://example.com"
      }
    
  • Response:

      {
        "shortID": "abc123"
      }
    

Get Analytics

  • Endpoint: GET /url/analytics/:shortid

  • Response:

      {
        "totalclicks": 10,
        "visithistory": [
          {
            "timestamp": "2023-10-01T12:00:00.000Z"
          }
        ]
      }
    

6. Code Snippets

index.js

const express = require('express');
const path = require('path');
const app = express();
const urlrouter = require("./routes/url");
const { connectDB } = require('./connect');
const URL = require('./models/url');
const staticRoute = require('./routes/staticRouter');
const port = 3000;

app.set('view engine', 'ejs');
app.set('views', path.resolve("./views"));
connectDB('mongodb://localhost:27017/urlshortener').then(() => {
    console.log('Connected to DB');
});
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use('/url', urlrouter);
app.use('/', staticRoute);
app.get('/url/:shortid', async (req, res) => {
    const shortid = req.params.shortid;
    const entry = await URL.findOneAndUpdate({
        shortID: shortid
    },
    {
        $inc: { totalclicks: 1 },
        $push: { visithistory: { timestamp: new Date().toISOString() } }
    });
    res.redirect(entry.redirectURL);
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

connect.js

const mongoose = require('mongoose');

async function connectDB(url) {
    return mongoose.connect(url);
}

module.exports = {
    connectDB,
};

models/url.js

const mongoose = require('mongoose');
const urlschema = new mongoose.Schema({
    shortID: {
        type: String,
        required: true,
        unique: true
    },
    redirectURL: {
        type: String,
        required: true
    },
    totalclicks: {
        type: Number,
        required: true
    },
    visithistory: [{
        timestamp: {
            type: Date,
            required: true
        }
    }]
}, { timestamps: true });

const URL = mongoose.model('url', urlschema);
module.exports = URL;

controllers/url.js

const { nanoid } = require('nanoid');
const URL = require('../models/url');

async function handleGenerateShorturl(req, res) {
    const body = req.body;
    if (!body.url) return res.status(400).json({ message: 'URL is required' });
    const shortID = nanoid(4);
    await URL.create({
        shortID: shortID,
        redirectURL: body.url,
        totalclicks: 0,
        visithistory: []
    });
    return res.render('home', { id: shortID });
}

async function handleGetAnalytics(req, res) {
    const shortID = req.params.shortid;
    const result = await URL.findOne({ shortID });
    return res.json({
        totalclicks: result.totalclicks,
        visithistory: result.visithistory
    });
}

module.exports = {
    handleGenerateShorturl,
    handleGetAnalytics
};

routes/url.js

const express = require('express');
const { handleGenerateShorturl, handleGetAnalytics } = require('../controllers/url');
const router = express.Router();
router.post('/', handleGenerateShorturl);
router.get('/analytics/:shortid', handleGetAnalytics);
module.exports = router;

routes/staticRouter.js

const express = require('express');
const URL = require('../models/url');
const router = express.Router();

router.get("/", async (req, res) => {
    const allurls = await URL.find({});
    return res.render('home', { urls: allurls });
});

module.exports = router;

views/home.ejs

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>

body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    margin: 0;
    padding: 20px;
    color: #333;
}

h1 {
    color: #444;
    text-align: center;
}

form {
    background-color: #fff;
    padding: 20px;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    max-width: 500px;
    margin: 20px auto;
}

label {
    display: block;
    margin-bottom: 8px;
    font-weight: bold;
}

input[type="text"] {
    width: calc(100% - 22px);
    padding: 10px;
    margin-bottom: 20px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 16px;
}

button {
    background-color: #28a745;
    color: white;
    padding: 10px 20px;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 16px;
}

button:hover {
    background-color: #218838;
}

/* Table Styles */
table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 20px;
    background-color: #fff;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

th, td {
    padding: 12px;
    text-align: left;
    border-bottom: 1px solid #ddd;
}

th {
    background-color: #f8f9fa;
    font-weight: bold;
}

tr:hover {
    background-color: #f1f1f1;
}

/* URL Generated Message */
p {
    background-color: #d1ecf1;
    color: #0c5460;
    padding: 10px;
    border-radius: 4px;
    text-align: center;
    max-width: 500px;
    margin: 20px auto;
}
</style>
<body>

    <% if (locals.id) { %>
        <p>Url generated: http://localhost:3000/url/<%= locals.id %></p>
    <% } %>

    <h1>Home Page</h1>
    <form method="post" action="/url">
        <label for="">Enter your original url</label>
        <input type="text" name="url" placeholder="https://example.com">
        <button type="submit">generate</button>
    </form>
    <div>
        <% if(locals.urls) { %>
            <table>
                <thead>
                  <th>S. No</th>
                  <th>ShortID</th>
                  <th>Redirect</th>
                  <th>Clicks</th>
                </thead>
                <tbody>
                  <% urls.forEach((url, index) => { %>
                    <tr>
                      <td><%= index + 1 %></td>
                      <td><a href="http://localhost:3000/url/<%= url.shortID %>" target="_blank">http://localhost:3000/url/<%= url.shortID %></a></td>
                      <td><%= url.redirectURL %></td>
                      <td><%= url.totalclicks %></td>
                    </tr>
                  <% }) %>
                </tbody>
            </table>
        <% } %>        
    </div>
</body>
</html>

7. Contributing

Contributions are welcome! Please follow these steps to contribute:

  1. Fork the repository.

  2. Create a new branch for your feature or bugfix.

  3. Commit your changes and push to the branch.

  4. Submit a pull request.