Skip to content

adityack477/bitlinks-v2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 

Repository files navigation

🔗 BitLinks v2.0

A Full-Stack URL Shortener with Analytics - Built with the MERN Stack

Live Demo Backend GitHub

MongoDB Express React Node.js TailwindCSS JWT


📖 The Story Behind This Project

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.


🆚 v1 vs v2 — What Changed

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

✨ Features

  • 🔐 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

🏗️ Architecture

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

🛠️ Tech Stack

Frontend

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

Backend

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

Infrastructure

Service Purpose Plan
MongoDB Atlas Cloud database Free M0 cluster
Render Backend hosting Free web service
Vercel Frontend hosting Free hobby plan

📡 API Reference

Authentication

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

URLs

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

Redirect

Method Endpoint Auth Required Description
GET /:shortCode Redirect to original URL + log click

Health Check

Method Endpoint Description
GET /api/health Returns server status and timestamp

🚀 Running Locally

Prerequisites

1. Clone the repository

git clone https://github.com/adityack477/bitlinks-v2.git
cd bitlinks-v2

2. Set up the Backend

cd backend
npm install
cp .env.example .env

Open 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=development

Start the backend:

npm run dev
# Server running at http://localhost:5000

3. Set up the Frontend

Open a new terminal:

cd frontend
npm install
cp .env.example .env

Open frontend/.env:

REACT_APP_API_URL=http://localhost:5000/api
REACT_APP_BASE_URL=http://localhost:5000

Start the frontend:

npm start
# App running at http://localhost:3000

4. Open in Browser

Go to http://localhost:3000 — both servers must be running simultaneously.


☁️ Deployment

The application is deployed across three separate services:

MongoDB Atlas (Database)

  • Free M0 cluster on AWS
  • Network access set to 0.0.0.0/0 to allow connections from Render's dynamic IPs
  • Database user with read/write permissions

Render (Backend)

  • 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.

Vercel (Frontend)

  • Framework: Create React App
  • Root directory: frontend
  • Environment variables: REACT_APP_API_URL, REACT_APP_BASE_URL
  • Auto-deploys on every push to the main branch

🔑 Key Technical Decisions

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.


📈 What I Learned Building This

  • 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 shortCode for O(log n) redirect lookups)
  • CORS configuration and why exact string matching matters (trailing slash bug)
  • 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

📄 License

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

🌐 Live Demo · 📁 GitHub · 🔧 API Health

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors