Complete Guide: Create a TikTok Clone Website (Frontend + Backend + Contact Form)
Type Here to Get Search Results !

Complete Guide: Create a TikTok Clone Website (Frontend + Backend + Contact Form)

 



🎥 Complete Guide: Create a TikTok Clone Website (Frontend + Backend + Contact Form)

🧠 Introduction

Aaj hum ek TikTok Clone Website banayenge jisme users vertically scroll hone wale short videos dekh sakte hain — bilkul real TikTok app jaisa!
Is guide mein hum frontend (HTML, CSS, JS) aur backend (Node.js + Express) dono create karenge.
End mein hum Contact Form bhi add karenge jahan visitors aapko directly message bhej sakte hain 📩


🧩 Step 1: Folder Setup

Apne system mein ek folder banao jiska naam ho:

tiktok-clone/

Iske andar ye structure create karo 👇

/tiktok-clone
   ├── /public
   │    ├── style.css
   │    ├── script.js
   │    ├── contact.html
   │    └── videos/
   ├── index.html
   ├── server.js
   └── package.json

🎨 Step 2: Frontend Code

🧱 index.html

Is file ko /tiktok-clone/ ke andar banao aur ye code paste karo 👇
(Main simplified UI + backend fetch + contact link include kar raha hoon)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>TikTok Clone</title>
  <link rel="stylesheet" href="public/style.css" />
</head>
<body>
  <div class="top-nav">
    <div class="nav-tabs">
      <div class="nav-tab">Following</div>
      <div class="nav-tab active">For You</div>
    </div>
  </div>

  <div id="videoFeed" class="app-container"></div>

  <div class="bottom-nav">
    <div class="nav-item active">🏠 Home</div>
    <div class="nav-item">👫 Friends</div>
    <div class="nav-item">
      <a href="public/contact.html" style="color:white; text-decoration:none;">📩 Contact</a>
    </div>
    <div class="nav-item">👤 Profile</div>
  </div>

  <script src="public/script.js"></script>
</body>
</html>

🎨 style.css

File: /public/style.css

* { margin: 0; padding: 0; box-sizing: border-box; }
body {
  font-family: 'Segoe UI', sans-serif;
  background: #000;
  color: #fff;
  overflow: hidden;
}
.app-container {
  height: 100vh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
}
.video-container {
  position: relative;
  height: 100vh;
  scroll-snap-align: start;
}
video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.video-info {
  position: absolute;
  bottom: 100px;
  left: 20px;
  right: 80px;
}
.username { font-weight: bold; margin-bottom: 8px; }
.description { font-size: 14px; line-height: 1.4; }
.music { font-size: 13px; opacity: 0.8; }
.action-sidebar {
  position: absolute;
  right: 16px;
  bottom: 100px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
}
.action-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
}
.action-icon {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background: rgba(255,255,255,0.15);
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 4px;
}
.action-count {
  font-size: 12px;
  font-weight: 600;
}
.bottom-nav {
  position: fixed;
  bottom: 0;
  left: 0; right: 0;
  background: rgba(0,0,0,0.9);
  display: flex;
  justify-content: space-around;
  padding: 10px;
  border-top: 1px solid rgba(255,255,255,0.1);
}
.nav-item { font-size: 13px; opacity: 0.8; }
.nav-item.active { color: #fff; font-weight: bold; opacity: 1; }
.contact-container {
  max-width: 400px;
  margin: 60px auto;
  padding: 20px;
  background: rgba(255,255,255,0.05);
  border-radius: 8px;
}
.contact-container h2 { text-align: center; margin-bottom: 20px; }
.contact-container input, .contact-container textarea {
  width: 100%;
  padding: 10px;
  margin-bottom: 10px;
  background: rgba(255,255,255,0.1);
  border: none;
  color: #fff;
  border-radius: 4px;
}
.contact-container button {
  width: 100%;
  background: #FE2C55;
  border: none;
  color: #fff;
  padding: 10px;
  font-weight: bold;
  border-radius: 4px;
  cursor: pointer;
}
.contact-container button:hover { background: #ff466e; }

💬 contact.html

File: /public/contact.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Contact Us</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <div class="contact-container">
    <h2>Contact Us</h2>
    <form id="contactForm">
      <input type="text" name="name" placeholder="Your Name" required />
      <input type="email" name="email" placeholder="Your Email" required />
      <textarea name="message" rows="5" placeholder="Your Message" required></textarea>
      <button type="submit">Send Message</button>
    </form>
    <p id="responseMsg" style="text-align:center;margin-top:10px;"></p>
  </div>

  <script>
    const form = document.getElementById('contactForm');
    const responseMsg = document.getElementById('responseMsg');
    
    form.addEventListener('submit', async (e) => {
      e.preventDefault();
      const data = {
        name: form.name.value,
        email: form.email.value,
        message: form.message.value
      };
      
      const res = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });
      
      const result = await res.json();
      responseMsg.textContent = result.message;
      form.reset();
    });
  </script>
</body>
</html>

🎬 script.js

File: /public/script.js

const videoFeed = document.getElementById('videoFeed');

async function loadVideos() {
  const response = await fetch('/api/videos');
  const videos = await response.json();

  videos.forEach(video => {
    const container = document.createElement('div');
    container.className = 'video-container';
    container.innerHTML = `
      <video src="${video.videoSrc}" loop muted playsinline></video>
      <div class="video-info">
        <div class="username">${video.username}</div>
        <div class="description">${video.description}</div>
        <div class="music">🎵 ${video.music}</div>
      </div>
      <div class="action-sidebar">
        <div class="action-btn"><div class="action-icon">❤️</div><div class="action-count">${video.likes}</div></div>
        <div class="action-btn"><div class="action-icon">💬</div><div class="action-count">${video.comments}</div></div>
      </div>
    `;
    videoFeed.appendChild(container);
  });
}
loadVideos();

⚙️ Step 3: Backend Setup (Node.js + Express)

1️⃣ Install Required Packages

Open terminal aur ye commands run karo 👇

cd tiktok-clone
npm init -y
npm install express cors nodemailer

2️⃣ server.js

File: /tiktok-clone/server.js

const express = require('express');
const cors = require('cors');
const nodemailer = require('nodemailer');
const app = express();
const PORT = 5000;

app.use(cors());
app.use(express.json());
app.use(express.static('public'));

// Sample video data
const videos = [
  {
    id: 1,
    username: '@dancer_moves',
    description: 'New dance challenge! 💃',
    music: 'Original Sound - dancer_moves',
    likes: '324.5K',
    comments: '12.3K',
    videoSrc: '/videos/sample1.mp4'
  },
  {
    id: 2,
    username: '@foodie_life',
    description: '5-Minute Pasta 🍝',
    music: 'Chill Beats',
    likes: '892.1K',
    comments: '45.6K',
    videoSrc: '/videos/sample2.mp4'
  }
];

// Video API
app.get('/api/videos', (req, res) => {
  res.json(videos);
});

// Contact Form API
app.post('/api/contact', async (req, res) => {
  const { name, email, message } = req.body;

  // Configure mail transporter (use your Gmail credentials or custom SMTP)
  const transporter = nodemailer.createTransport({
    service: 'gmail',
    auth: {
      user: 'yourgmail@gmail.com',
      pass: 'your_app_password' // use App Password, not your real Gmail password
    }
  });

  const mailOptions = {
    from: email,
    to: 'yourgmail@gmail.com',
    subject: `New Contact from ${name}`,
    text: message
  };

  try {
    await transporter.sendMail(mailOptions);
    res.json({ message: 'Message sent successfully! ✅' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: 'Failed to send message ❌' });
  }
});

app.listen(PORT, () => console.log(`✅ Server running at http://localhost:${PORT}`));

🚀 Step 4: Run and Test

Terminal mein command run karo 👇

node server.js

Phir open karo:
👉 http://localhost:5000

🎬 Ab tumhara TikTok Clone Website open ho jayega
📩 “Contact” tab par click karo — message send karne ka form dikhega


🌐 Bonus: Deploy Online

  • Frontend: Netlify / Vercel
  • Backend: Render / Railway
  • Email: Gmail App Password enable karke use karo

🎯 Advanced Full Stack TikTok Clone (with MongoDB + Upload + Contact Messages)

🧠 Overview

Is version mein hum 3 main backend features add karenge:

  1. 🧩 MongoDB Database (Mongoose ke through)
  2. 🎬 Video Upload API (Multer ke through)
  3. 💬 Contact Form Data Storage

⚙️ Step 1: New Packages Install Karo

Terminal mein project folder ke andar yeh run karo 👇

npm install mongoose multer

🧩 Step 2: MongoDB Setup

A. Local MongoDB (optional)

Agar aapne pehle MongoDB Community Server install kiya tha, to wo local pe chalega.

Start command (agar chahiye):

mongod

B. MongoDB Atlas (Cloud)

Agar aapne Atlas setup kiya hai, to connection string kuch aise milegi 👇

mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net/tiktokClone

🧱 Step 3: MongoDB Models Create Karo

File banao:
📁 /models/Video.js
📁 /models/Contact.js

/models/Video.js

const mongoose = require('mongoose');

const videoSchema = new mongoose.Schema({
  username: String,
  description: String,
  music: String,
  likes: String,
  comments: String,
  videoSrc: String
});

module.exports = mongoose.model('Video', videoSchema);

/models/Contact.js

const mongoose = require('mongoose');

const contactSchema = new mongoose.Schema({
  name: String,
  email: String,
  message: String,
  date: { type: Date, default: Date.now }
});

module.exports = mongoose.model('Contact', contactSchema);

🧠 Step 4: Updated server.js (Complete Backend with Database + Upload + Contact)

Replace your old server.js with this 👇

const express = require('express');
const cors = require('cors');
const mongoose = require('mongoose');
const multer = require('multer');
const nodemailer = require('nodemailer');
const path = require('path');

const Video = require('./models/Video');
const Contact = require('./models/Contact');

const app = express();
const PORT = 5000;

// Middleware
app.use(cors());
app.use(express.json());
app.use(express.static('public'));

// MongoDB Connection
mongoose.connect('mongodb+srv://<username>:<password>@cluster0.mongodb.net/tiktokClone')
  .then(() => console.log('✅ MongoDB Connected'))
  .catch(err => console.error('❌ MongoDB Error:', err));

// Multer Setup for Video Uploads
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'public/videos');
  },
  filename: (req, file, cb) => {
    cb(null, Date.now() + path.extname(file.originalname));
  }
});
const upload = multer({ storage });

// 📦 API ROUTES

// Upload New Video
app.post('/api/upload', upload.single('video'), async (req, res) => {
  try {
    const { username, description, music } = req.body;
    const newVideo = new Video({
      username,
      description,
      music,
      likes: '0',
      comments: '0',
      videoSrc: `/videos/${req.file.filename}`
    });
    await newVideo.save();
    res.json({ message: 'Video uploaded successfully!', video: newVideo });
  } catch (error) {
    res.status(500).json({ message: 'Upload failed!', error });
  }
});

// Fetch All Videos
app.get('/api/videos', async (req, res) => {
  const videos = await Video.find();
  res.json(videos);
});

// Save Contact Message
app.post('/api/contact', async (req, res) => {
  const { name, email, message } = req.body;

  // Save to MongoDB
  const contact = new Contact({ name, email, message });
  await contact.save();

  // Send Email Notification (optional)
  const transporter = nodemailer.createTransport({
    service: 'gmail',
    auth: {
      user: 'yourgmail@gmail.com',
      pass: 'your_app_password'
    }
  });

  const mailOptions = {
    from: email,
    to: 'yourgmail@gmail.com',
    subject: `New Contact Message from ${name}`,
    text: `${message}\n\nEmail: ${email}`
  };

  try {
    await transporter.sendMail(mailOptions);
    res.json({ message: 'Message sent and saved successfully! ✅' });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: 'Error sending email ❌' });
  }
});

// Start Server
app.listen(PORT, () => console.log(`🚀 Server running at http://localhost:${PORT}`));

📤 Step 5: Video Upload Test (Thunder Client / Postman)

  1. VS Code → Thunder Client open karo
  2. New Request → POST http://localhost:5000/api/upload
  3. “Body” → “Form Data” select karo
    • Key: video → (Select File)
    • Key: username → @nazim_mustafa
    • Key: description → This is my first TikTok video
    • Key: music → Original Sound
  4. Send karo ✅
    Agar response “Video uploaded successfully!” aaye — upload ho gaya 🎉

📩 Step 6: Contact Form Test

Browser → http://localhost:5000/public/contact.html
Form fill karo aur “Send Message” dabao →

  • Message database me save hoga
  • Aapke Gmail par bhi notification aayegi

🗃️ Step 7: Check Data

Videos Collection:

MongoDB Atlas → tiktokClonevideos

Contacts Collection:

MongoDB Atlas → tiktokClonecontacts

Sab data real-time stored rahega ✅


🌐 Step 8: Deploy Free

Frontend: Netlify / Vercel
Backend: Render / Railway
MongoDB: Atlas (Already Cloud)


🧠 Final Result

✅ Real working TikTok Clone
✅ Full-stack backend (Node.js + MongoDB + Express)
✅ Video upload & display system
✅ Contact form with email + database save
✅ Ready for live deployment


📬 Contact Developer

Agar aapko is project ka custom version, admin panel, ya video upload dashboard chahiye —
to aap mujhe yahan contact kar sakte hain 👇

📧 Email: nazimmustafa911@gmail.com
💬 WhatsApp (for collaboration only): +92-315784772
🌐 Portfolio: [coming soon...]


🔧 Admin Dashboard — Upload / Delete / Analytics (Ready-to-paste)

Accha! ab main aapke TikTok Clone project ke liye ek simple, secure-ish Admin Dashboard likh ke de raha hoon — jisme aap videos upload kar sakte ho, videos delete kar sakte ho, aur kuch basic analytics (total videos, total contacts) dekh sakte ho.
Yeh Blogger-ready / repo-ready code hai — seedha copy-paste karke apne project mein laga lo.

Security note: Yeh dashboard development / small private use ke liye hai. Production ke liye proper auth (JWT, OAuth), rate limiting, HTTPS, strong password management aur role-based access control zaroor setup karein.


1) .env — Admin key add karo

/tiktok-clone/.env file mein yeh values add karo (agar already hai to add/update karo):

PORT=5000
MONGO_URI=mongodb+srv://<username>:<password>@cluster0.mongodb.net/tiktokClone
ADMIN_KEY=some-very-strong-secret-key-please-change
EMAIL_USER=yourgmail@gmail.com
EMAIL_PASS=your_app_password

ADMIN_KEY ko strong rakho. Admin dashboard requests me isse header x-admin-key ke through bhejna hoga.


2) Backend: admin middleware + admin routes

A) middlewares/adminAuth.js

Create folder /middlewares and file adminAuth.js:

// middlewares/adminAuth.js
require('dotenv').config();

module.exports = function (req, res, next) {
  const key = req.headers['x-admin-key'] || req.query.adminKey;
  if (!key || key !== process.env.ADMIN_KEY) {
    return res.status(401).json({ message: 'Unauthorized - invalid admin key' });
  }
  next();
};

B) routes/adminRoutes.js

Create /routes/adminRoutes.js:

// routes/adminRoutes.js
const express = require('express');
const router = express.Router();
const AdminAuth = require('../middlewares/adminAuth');
const Video = require('../models/Video');
const Contact = require('../models/Contact');
const fs = require('fs');
const path = require('path');

// Protected route: get all videos (admin view)
router.get('/videos', AdminAuth, async (req, res) => {
  try {
    const videos = await Video.find().sort({ createdAt: -1 });
    res.json(videos);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// Protected route: delete a video by id (also remove file)
router.delete('/videos/:id', AdminAuth, async (req, res) => {
  try {
    const vid = await Video.findById(req.params.id);
    if (!vid) return res.status(404).json({ message: 'Video not found' });

    // delete file from public/videos if exists (only if local storage)
    if (vid.videoSrc && vid.videoSrc.startsWith('/videos/')) {
      const filePath = path.join(__dirname, '..', 'public', vid.videoSrc);
      fs.unlink(filePath, (err) => {
        // ignore error (maybe file not present)
      });
    }

    await vid.remove();
    res.json({ message: 'Video deleted successfully' });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// Protected route: stats (simple analytics)
router.get('/stats', AdminAuth, async (req, res) => {
  try {
    const totalVideos = await Video.countDocuments();
    const totalContacts = await (require('../models/Contact')).countDocuments();
    // Additional aggregates example: top 5 videos by likes (if likes numeric)
    const topVideos = await Video.find().sort({ likes: -1 }).limit(5);
    res.json({ totalVideos, totalContacts, topVideos });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

C) server.js — admin routes register karo

Aapke existing server.js me ye lines add/update karo:

// at top (after other requires)
const adminRoutes = require('./routes/adminRoutes');

// ... existing middleware & code ...

// register admin routes under /api/admin
app.use('/api/admin', adminRoutes);

// rest of your routes...

3) Frontend Admin Panel (static HTML + JS)

Add files under public/admin.html and public/admin.js. Ye simple single-page admin panel hai — upload is still via existing /api/upload route, but admin panel shows video list, delete buttons, and stats.

A) public/admin.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Admin Dashboard - TikTok Clone</title>
  <link rel="stylesheet" href="admin.css" />
  <style>
    /* small inline CSS so you can paste quickly */
    body { font-family: Arial, sans-serif; background:#0b0b0b; color:#fff; padding:20px; }
    .container{ max-width:1100px; margin:0 auto; }
    h1{ margin-bottom:10px; }
    .row{ display:flex; gap:20px; flex-wrap:wrap; }
    .card{ background: rgba(255,255,255,0.03); padding:16px; border-radius:8px; flex:1; min-width:260px; }
    input, textarea, button, select { width:100%; padding:8px; margin-top:8px; border-radius:6px; border:1px solid rgba(255,255,255,0.08); background:transparent; color:#fff; }
    table { width:100%; border-collapse:collapse; margin-top:12px; }
    th, td { padding:8px; text-align:left; border-bottom:1px solid rgba(255,255,255,0.06); font-size:14px; }
    .btn { padding:8px 12px; cursor:pointer; border:none; border-radius:6px; }
    .btn-danger { background:#ff4d4d; color:#fff; }
    .btn-primary { background:#2d9cdb; color:#fff; }
    .small { font-size:13px; color:rgba(255,255,255,0.7); }
    video { width:120px; height:70px; object-fit:cover; border-radius:6px; }
    .top-right { float:right; }
    .stat{ font-size:18px; font-weight:700; }
  </style>
</head>
<body>
  <div class="container">
    <h1>Admin Dashboard <span class="small">(Private)</span></h1>

    <div class="row">
      <div class="card" style="flex: 0 0 320px;">
        <h3>Upload Video</h3>
        <form id="uploadForm">
          <input type="text" name="username" placeholder="Username e.g. @nazim_mustafa" required />
          <input type="text" name="description" placeholder="Description" required />
          <input type="text" name="music" placeholder="Music / Sound name" />
          <input type="file" name="video" accept="video/*" required />
          <button class="btn btn-primary" type="submit">Upload</button>
          <p id="uploadMsg" class="small"></p>
        </form>
      </div>

      <div class="card">
        <h3>Analytics <span id="refreshStats" class="small top-right" style="cursor:pointer">Refresh</span></h3>
        <div style="display:flex;gap:16px;margin-top:8px;">
          <div>
            <div class="stat" id="totalVideos">0</div>
            <div class="small">Total Videos</div>
          </div>
          <div>
            <div class="stat" id="totalContacts">0</div>
            <div class="small">Total Contacts</div>
          </div>
        </div>
        <h4 style="margin-top:12px">Top Videos (by likes)</h4>
        <div id="topVideosList" class="small"></div>
      </div>
    </div>

    <div class="card" style="margin-top:18px;">
      <h3>All Videos</h3>
      <table id="videosTable">
        <thead>
          <tr><th>Preview</th><th>Username</th><th>Description</th><th>Likes</th><th>Actions</th></tr>
        </thead>
        <tbody></tbody>
      </table>
    </div>

  </div>

  <script src="admin.js"></script>
</body>
</html>

B) public/admin.js

// public/admin.js
const ADMIN_KEY = prompt("Enter admin key (will be sent with requests):"); // quick auth prompt

if (!ADMIN_KEY) {
  alert('Admin key required. Reload to try again.');
}

// helper: send fetch with admin key
async function adminFetch(url, opts = {}) {
  opts.headers = {
    ...(opts.headers || {}),
    'x-admin-key': ADMIN_KEY,
    'Accept': 'application/json'
  };
  const res = await fetch(url, opts);
  if (res.status === 401) {
    alert('Unauthorized: invalid admin key');
    throw new Error('Unauthorized');
  }
  return res;
}

// load stats
async function loadStats() {
  try {
    const res = await adminFetch('/api/admin/stats');
    const data = await res.json();
    document.getElementById('totalVideos').innerText = data.totalVideos;
    document.getElementById('totalContacts').innerText = data.totalContacts;
    const top = data.topVideos || [];
    const el = document.getElementById('topVideosList');
    el.innerHTML = top.map(v => `<div style="margin-bottom:6px"><strong>${v.username}</strong> — ${v.likes} likes</div>`).join('') || '<div class="small">No data</div>';
  } catch (err) {
    console.error(err);
  }
}

// load all videos into table
async function loadVideos() {
  try {
    const res = await adminFetch('/api/admin/videos');
    const videos = await res.json();
    const tbody = document.querySelector('#videosTable tbody');
    tbody.innerHTML = '';
    videos.forEach(v => {
      const tr = document.createElement('tr');
      tr.innerHTML = `
        <td><video src="${v.videoSrc}" muted></video></td>
        <td>${v.username}</td>
        <td>${v.description}</td>
        <td>${v.likes}</td>
        <td>
          <button class="btn btn-danger" data-id="${v._id}">Delete</button>
        </td>
      `;
      tbody.appendChild(tr);
    });

    // wire delete buttons
    document.querySelectorAll('button[data-id]').forEach(btn => {
      btn.addEventListener('click', async () => {
        if (!confirm('Delete this video?')) return;
        const id = btn.getAttribute('data-id');
        try {
          const res = await adminFetch(`/api/admin/videos/${id}`, { method: 'DELETE' });
          const json = await res.json();
          alert(json.message || 'Deleted');
          await loadVideos();
          await loadStats();
        } catch (err) {
          console.error(err);
          alert('Delete failed');
        }
      });
    });

  } catch (err) {
    console.error(err);
  }
}

// upload form handling (uses existing /api/upload route)
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
  e.preventDefault();
  const form = e.target;
  const data = new FormData(form);
  // send admin key on query param for upload route which may not read headers for multipart
  try {
    document.getElementById('uploadMsg').innerText = 'Uploading...';
    const res = await fetch(`/api/upload?adminKey=${ADMIN_KEY}`, {
      method: 'POST',
      body: data
    });
    const json = await res.json();
    if (res.ok) {
      document.getElementById('uploadMsg').innerText = json.message || 'Uploaded';
      form.reset();
      await loadVideos();
      await loadStats();
    } else {
      document.getElementById('uploadMsg').innerText = json.message || 'Upload failed';
    }
  } catch (err) {
    console.error(err);
    document.getElementById('uploadMsg').innerText = 'Upload error';
  }
});

// init
loadVideos();
loadStats();
document.getElementById('refreshStats').addEventListener('click', loadStats);

Note: admin.js uses prompt() for quick admin key. For better UX, replace with login form and secure token storage.


4) Backend: accept adminKey in upload route (for admin uploads)

If you want admin uploads only, update upload route in server.js to allow adminKey via query (multipart forms can't send x-admin-key header easily in some clients):

In your /api/upload route (where you use multer), add a check:

// inside app.post('/api/upload', upload.single('video'), async (req,res) => { ... })
const adminKeyFromQuery = req.query.adminKey;
if (process.env.ADMIN_KEY && adminKeyFromQuery !== process.env.ADMIN_KEY) {
  return res.status(401).json({ message: 'Unauthorized - invalid admin key' });
}

Or you can read headers if your client supports sending headers with FormData.


5) Start the server & open admin panel

  1. Start server:
node server.js
  1. Open admin panel in browser:
http://localhost:5000/admin.html

When page loads it will prompt for the ADMIN_KEY — enter the same value from .env (ADMIN_KEY=...).


6) Extra suggestions (production-ready improvements)

  • Replace prompt() with a secure login page that returns a short-lived JWT.
  • Use HTTPS & strong password policies.
  • Move uploaded files to a cloud storage (Cloudinary, S3) and store only URLs in DB.
  • Add pagination for video list.
  • Add role audit logs (who deleted/uploaded).
  • Add rate limiting and CSRF protection.

7) Full file checklist (what you must have)

  • .env (with ADMIN_KEY, MONGO_URI, etc.)
  • middlewares/adminAuth.js
  • routes/adminRoutes.js
  • public/admin.html
  • public/admin.js
  • Optional: public/admin.css (if you want external CSS)
  • Updated server.js with app.use('/api/admin', adminRoutes); and /api/upload adminKey check



Post a Comment

0 Comments
* Please Don't Spam Here. All the Comments are Reviewed by Admin.

Top Post Ad

Below Post Ad