This project started as a learning exercise while following CodeWithHarry's web development series. The original version (v1) was a simple Next.js app — my first real full-stack project where I was just getting comfortable with React, APIs, and MongoDB.
After gaining more experience, I rebuilt it from scratch as BitLinks v2 — a production-grade application using a properly separated MERN stack with authentication, analytics, rate limiting, and a completely redesigned UI. The goal was to apply everything I had learned and build something I'd actually be proud to put on my resume.
v1 → v2 isn't just a redesign. It's the difference between following a tutorial and actually understanding what you're building.
| Feature | v1 (Original) | v2 (This Version) |
|---|---|---|
| Architecture | Next.js monolith (frontend + backend mixed) | Separate React frontend + Express backend |
| Authentication | ❌ None — anyone could create links | ✅ JWT-based register/login with bcrypt |
| Dashboard | ❌ No way to manage your links | ✅ Full dashboard — view, search, delete links |
| Analytics | ❌ No click tracking | ✅ Every click timestamped + bar chart visualization |
| Notifications | ❌ Native browser alert() popups |
✅ Toast notifications via react-hot-toast |
| Input Validation | ❌ None | ✅ Frontend + backend validation with error messages |
| Rate Limiting | ❌ None | ✅ 100 requests/15 min per IP |
| Short Code | Custom only | ✅ Custom or auto-generated via nanoid |
| Guest Mode | N/A | ✅ Shorten without account |
| Soft Delete | ❌ Hard delete | ✅ Deactivates link, preserves analytics data |
| Password Security | ❌ N/A | ✅ bcrypt with salt (cost factor 12) |
| UI Design | Basic purple boxes | ✅ Professional responsive design |
| Deployment | Not deployed | ✅ Live on Vercel + Render + MongoDB Atlas |
- 🔐 User Authentication — Register and login with JWT tokens. Passwords hashed with bcrypt (never stored as plain text)
- ⚡ Instant URL Shortening — Works for guests too. Auto-generate a 7-char code or choose your own custom code
- 📊 Click Analytics — Every redirect is timestamped. Dashboard shows total clicks and a daily bar chart per link
- 🎛️ Personal Dashboard — View all your links, search through them, see click counts, delete links
- 🛡️ Rate Limiting — 100 requests per 15 minutes per IP address to prevent abuse
- 📋 Copy to Clipboard — One-click copy of your short URL
- 📱 Fully Responsive — Works on mobile, tablet, and desktop
- 🔔 Toast Notifications — Clean feedback instead of ugly browser alerts
- 🌐 Guest Mode — Try the shortener without creating an account
This project is split into two completely independent applications that communicate via a REST API.
bitlinks-v2/
│
├── backend/ ← Node.js + Express REST API
│ ├── server.js ← Entry point, middleware, DB connection
│ ├── models/
│ │ ├── Url.js ← URL schema + click tracking array + indexes
│ │ └── User.js ← User schema + bcrypt pre-save hook
│ ├── controllers/
│ │ ├── authController.js ← register(), login(), getMe()
│ │ └── urlController.js ← createShortUrl(), getUserUrls(), deleteUrl(), getAnalytics()
│ ├── routes/
│ │ ├── authRoutes.js ← /api/auth/*
│ │ ├── urlRoutes.js ← /api/urls/*
│ │ └── redirectRoute.js ← /:shortCode → 302 redirect + log click
│ └── middleware/
│ └── auth.js ← JWT protect + optionalAuth middleware
│
└── frontend/ ← React 18 + Tailwind CSS SPA
└── src/
├── context/
│ └── AuthContext.jsx ← Global auth state (no prop drilling)
├── pages/
│ ├── Home.jsx ← Landing page with features + CTA
│ ├── Shorten.jsx ← URL shortener form with validation
│ ├── Dashboard.jsx ← Links table + analytics modal + bar chart
│ ├── Login.jsx ← Sign in with password visibility toggle
│ ├── Register.jsx ← Sign up with live password strength indicator
│ └── NotFound.jsx ← 404 / expired link page
└── components/
└── Navbar.jsx ← Sticky responsive nav with mobile menu
| Technology | Version | Purpose |
|---|---|---|
| React | 18 | UI library |
| React Router | v6 | Client-side routing, protected routes |
| Tailwind CSS | v3 | Utility-first styling |
| Axios | ^1.6 | HTTP client with global auth interceptor |
| Recharts | ^2.12 | Click analytics bar chart |
| react-hot-toast | ^2.4 | Toast notification system |
| Technology | Version | Purpose |
|---|---|---|
| Node.js | v22 | JavaScript runtime |
| Express.js | ^4.18 | Web framework + REST API |
| Mongoose | ^8.2 | MongoDB ODM + schema validation |
| jsonwebtoken | ^9.0 | JWT generation and verification |
| bcryptjs | ^2.4 | Password hashing (cost factor 12) |
| nanoid | ^3.3 | Cryptographically secure short code generation |
| express-rate-limit | ^7.2 | IP-based rate limiting |
| cors | ^2.8 | Cross-origin resource sharing |
| Service | Purpose | Plan |
|---|---|---|
| MongoDB Atlas | Cloud database | Free M0 cluster |
| Render | Backend hosting | Free web service |
| Vercel | Frontend hosting | Free hobby plan |
| Method | Endpoint | Auth Required | Description |
|---|---|---|---|
POST |
/api/auth/register |
❌ | Create a new account |
POST |
/api/auth/login |
❌ | Sign in → returns JWT |
GET |
/api/auth/me |
✅ | Get currently logged-in user |
| Method | Endpoint | Auth Required | Description |
|---|---|---|---|
POST |
/api/urls |
Optional | Create a short URL |
GET |
/api/urls/my |
✅ | Get all links for the logged-in user |
DELETE |
/api/urls/:id |
✅ | Soft-delete a link |
GET |
/api/urls/analytics/:code |
✅ | Get click analytics for a link |
| Method | Endpoint | Auth Required | Description |
|---|---|---|---|
GET |
/:shortCode |
❌ | Redirect to original URL + log click |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/health |
Returns server status and timestamp |
- Node.js v18 or higher
- A MongoDB Atlas account (free)
git clone https://github.com/adityack477/bitlinks-v2.git
cd bitlinks-v2cd backend
npm install
cp .env.example .envOpen backend/.env and fill in your values:
PORT=5000
MONGODB_URI=mongodb+srv://username:password@cluster0.xxxxx.mongodb.net/bitlinks?retryWrites=true&w=majority
JWT_SECRET=your_long_random_secret_here
CLIENT_URL=http://localhost:3000
BASE_URL=http://localhost:5000
NODE_ENV=developmentStart the backend:
npm run dev
# Server running at http://localhost:5000Open a new terminal:
cd frontend
npm install
cp .env.example .envOpen frontend/.env:
REACT_APP_API_URL=http://localhost:5000/api
REACT_APP_BASE_URL=http://localhost:5000Start the frontend:
npm start
# App running at http://localhost:3000Go to http://localhost:3000 — both servers must be running simultaneously.
The application is deployed across three separate services:
- Free M0 cluster on AWS
- Network access set to
0.0.0.0/0to allow connections from Render's dynamic IPs - Database user with read/write permissions
- Service type: Web Service
- Root directory:
backend - Build command:
npm install - Start command:
node server.js - Environment variables:
MONGODB_URI,JWT_SECRET,CLIENT_URL,BASE_URL,NODE_ENV
Note: The free tier sleeps after 15 minutes of inactivity. The first request after sleeping takes ~30 seconds to respond — this is normal behavior on the free plan.
- Framework: Create React App
- Root directory:
frontend - Environment variables:
REACT_APP_API_URL,REACT_APP_BASE_URL - Auto-deploys on every push to the
mainbranch
Why 302 redirect instead of 301?
A 301 Moved Permanently is cached by the browser — future requests go directly to the destination, bypassing our server. We use 302 Found (temporary) so every visit hits the server, allowing us to log the click timestamp for analytics.
Why soft delete instead of hard delete?
When a user deletes a link, we set isActive: false instead of removing the document. This preserves all the click history data, which is useful for analytics. The redirect route only serves active links.
Why JWT over sessions? JWTs are stateless — the server doesn't need to store session data anywhere. This makes the backend horizontally scalable (you can run multiple instances without sharing state).
Why nanoid for short codes?
nanoid generates cryptographically secure URL-safe IDs. 7 characters using A-Za-z0-9_- gives 64^7 ≈ 4.3 trillion combinations — effectively collision-proof at our scale, and significantly shorter than a UUID.
- How to properly separate a full-stack app into independent frontend and backend services
- JWT authentication flow end-to-end (signing, storing, sending, verifying)
- Bcrypt password hashing and why plain-text passwords are never acceptable
- MongoDB indexing for performance (indexed
shortCodefor O(log n) redirect lookups) - CORS configuration and why exact string matching matters (
trailing slashbug) - React Context API for global state management without prop drilling
- Deploying to production and debugging real environment issues (IP whitelisting, env vars, CORS)
- The difference between development and production environments
MIT License — feel free to use this project as a reference or starting point.
Built with ❤️ by Aditya Kadam — evolved from a CodeWithHarry tutorial into a production-grade application