Project Idea: URL Shortener

This is a classic full-stack utility project. You will build a tool that takes a very long URL from a user and gives them back a short, unique link (e.g., `mysite.com/a8f3H`). When anyone visits that short link, your app will automatically redirect them to the original long URL. This project is fantastic for understanding the full MERN stack flow.

✨ Key Features

  • An input field to paste a long URL.
  • A "Shorten" button that generates a unique short code.
  • Display the new, short link to the user.
  • When the short link is visited, it must redirect to the original long URL.
  • (Bonus) Count how many times a link has been clicked.

💻 Tech Stack & Skills

  • Tech Stack: React (Frontend), Express/Node.js (Backend), MongoDB (Database), `nanoid` (for generating codes), `cors`.
  • Skills Practiced: Full-stack API, Database schema design, Backend routing & redirection, Form handling in React, State Management.
React React
Node.js Node.js
MongoDB MongoDB
Express Express

🛠️ Full Step-by-Step Tutorial

This project has two parts: the **Backend API** (Node/Express) that handles the logic and database, and the **Frontend App** (React) that the user interacts with.

Part 1: The Backend API (Node.js & Express)

Step 1.1: Setup Project

  1. Create a new folder named backend.
  2. Open a terminal in that folder and run npm init -y.
  3. Install the necessary packages:
    npm install express mongoose nanoid cors dotenv
  4. Install nodemon as a dev dependency:
    npm install -D nodemon

Step 1.2: Create the Server Entrypoint

Create a file named index.js.

backend/index.js
require('dotenv').config(); // Load .env variables
const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');

const app = express();
const PORT = process.env.PORT || 5000;

// Middlewares
app.use(cors());
app.use(express.json()); // To parse JSON request bodies

// Database Connection
mongoose.connect(process.env.MONGO_URI, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
})
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('Could not connect to MongoDB:', err));

// (Routes will go here)

app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

You'll also need a .env file in the backend folder:

backend/.env
MONGO_URI=your_mongodb_connection_string_here

(You can get a free string from MongoDB Atlas).

Step 1.3: Create the URL Model

Create a folder models and inside it, a file Url.js.

backend/models/Url.js
const mongoose = require('mongoose');

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

module.exports = mongoose.model('Url', urlSchema);

Step 1.4: Create the Routes

This is the most important part. We need two routes:

  1. POST /api/shorten: To create a new short URL.
  2. GET /:shortCode: To redirect to the long URL.

Create a folder routes and inside it, a file url.js.

backend/routes/url.js
const express = require('express');
const router = express.Router();
const { nanoid } = require('nanoid');
const Url = require('../models/Url');

// --- 1. The "Shorten" Route ---
router.post('/api/shorten', async (req, res) => {
    const { longUrl } = req.body;
    const baseUrl = process.env.BASE_URL || `http://localhost:${process.env.PORT || 5000}`;

    try {
        // Check if this long URL is already in the database
        let url = await Url.findOne({ longUrl });

        if (url) {
            // If it exists, return the existing short URL
            res.json({ shortUrl: `${baseUrl}/${url.shortCode}` });
        } else {
            // If not, create a new short code
            const shortCode = nanoid(7);
            
            // Create a new Url document
            url = new Url({
                longUrl,
                shortCode,
            });

            await url.save();
            res.status(201).json({ shortUrl: `${baseUrl}/${url.shortCode}` });
        }
    } catch (err) {
        console.error(err);
        res.status(500).json('Server error');
    }
});


// --- 2. The "Redirect" Route ---
// IMPORTANT: This must be a separate route handler
router.get('/:shortCode', async (req, res) => {
    try {
        const url = await Url.findOne({ shortCode: req.params.shortCode });

        if (url) {
            // Increment click count
            url.clicks++;
            await url.save();

            // Redirect to the original long URL
            return res.redirect(url.longUrl);
        } else {
            return res.status(404).json('No URL found');
        }
    } catch (err) {
        console.error(err);
        res.status(500).json('Server error');
    }
});

module.exports = router;

Step 1.5: Connect Routes in `index.js`

Add this line to your backend/index.js file (before app.listen):

backend/index.js (Add this line)
// Routes will go here
app.use('/', require('./routes/url'));

Your backend is now complete! You can run it with nodemon index.js.

Part 2: The Frontend (React)

Step 2.1: Setup Project

  1. In your main project folder (outside backend), run:
    npx create-react-app frontend
  2. cd frontend
  3. Install Axios: npm install axios

Step 2.2: The Main Component (`App.js`)

For this simple app, we can write all our logic in App.js.

frontend/src/App.js
import React, { useState } from 'react';
import axios from 'axios';
import './App.css'; // We will create this file

function App() {
    const [longUrl, setLongUrl] = useState('');
    const [shortUrl, setShortUrl] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);

    const handleSubmit = async (e) => {
        e.preventDefault();
        setLoading(true);
        setError(null);
        setShortUrl(null);

        try {
            // Make API request to our backend
            const response = await axios.post('http://localhost:5000/api/shorten', {
                longUrl: longUrl,
            });
            setShortUrl(response.data.shortUrl);
        } catch (err) {
            setError('Failed to shorten URL. Please try again.');
            console.error(err);
        } finally {
            setLoading(false);
        }
    };

    return (
        

URL Shortener

Enter a long URL to get a short link.

setLongUrl(e.target.value)} placeholder="https://example.com/my-very-long-url" required />
{error &&

{error}

} {shortUrl && (

Your short URL:

{shortUrl}
)}
); } export default App;

Step 2.3: Basic Styling (`App.css`)

Create frontend/src/App.css to make it look good.

frontend/src/App.css
.app-container {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 80vh;
    font-family: 'Inter', sans-serif;
}
/* We can re-use the .content-body and .highlight styles from the main site */
.highlight {
    background: linear-gradient(90deg, #f43f5e, #ec4899);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}
.url-form {
    display: flex;
    gap: 10px;
}
.url-form input {
    flex-grow: 1;
    padding: 12px 15px;
    font-size: 1rem;
    border-radius: 8px;
    border: 1px solid #334155;
    background-color: #1e293b;
    color: #fff;
}
.url-form button {
    padding: 12px 20px;
    font-size: 1rem;
    font-weight: 700;
    border: none;
    border-radius: 8px;
    background: linear-gradient(90deg, #a78bfa, #f472b6);
    color: #fff;
    cursor: pointer;
}
.url-form button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.result-display {
    margin-top: 30px;
    text-align: center;
}
.error-text {
    color: #f87171;
}
.short-url-result p {
    font-size: 1.1rem;
    margin-bottom: 10px;
}
.short-url-result a {
    font-size: 1.2rem;
    color: #38bdf8;
    font-weight: bold;
    word-break: break-all;
}

Done! Now, run your backend (nodemon index.js) and your frontend (npm start) in two separate terminals. Your MERN URL shortener should be working!

🔥 Next Steps & Challenges

After completing this tutorial, try these ideas:

  • **Click Tracking:** Create a new API route (`GET /api/stats/:shortCode`) that returns the `clicks` count for a link and display it on the frontend.
  • **User Accounts:** Add MERN authentication so users can sign up, log in, and see a dashboard of *all* the links they have shortened.
  • **Custom URLs:** Allow users to choose their own custom short code (e.g., `mysite.com/my-portfolio`) instead of a random one.
  • **Copy to Clipboard:** Add a button that copies the new short URL to the user's clipboard.