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.
🛠️ 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
- Create a new folder named
backend. - Open a terminal in that folder and run
npm init -y. - Install the necessary packages:
npm install express mongoose nanoid cors dotenv - Install
nodemonas a dev dependency:
npm install -D nodemon
Step 1.2: Create the Server Entrypoint
Create a file named 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:
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.
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:
POST /api/shorten: To create a new short URL.GET /:shortCode: To redirect to the long URL.
Create a folder routes and inside it, a file 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):
// 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
- In your main project folder (outside
backend), run:
npx create-react-app frontend cd frontend- 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.
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.
{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.
.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.