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
Step 2: Install Dependencies
Next, install the necessary dependencies:
npm install express mongoose cors dotenv
npm install --save-dev nodemon
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
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}`);
});
Create a .env
file in the root directory to store environment variables:
MONGODB_USER=yourMongoDBUsername
MONGODB_PASSWORD=yourMongoDBPassword
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);
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;
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 };
Running the Application
Start the server using the following command:
npm run dev
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!