🎥 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:
- 🧩 MongoDB Database (Mongoose ke through)
- 🎬 Video Upload API (Multer ke through)
- 💬 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)
- VS Code → Thunder Client open karo
- New Request →
POST http://localhost:5000/api/upload - “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
- 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 → tiktokClone → videos
Contacts Collection:
MongoDB Atlas → tiktokClone → contacts
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>
<!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.
// 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
- Start server:
node server.js
- Open admin panel in browser:
http://localhost:5000/admin.html
node server.js
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.
prompt() with a secure login page that returns a short-lived JWT.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
.env (with ADMIN_KEY, MONGO_URI, etc.)middlewares/adminAuth.jsroutes/adminRoutes.jspublic/admin.htmlpublic/admin.jspublic/admin.css (if you want external CSS)server.js with app.use('/api/admin', adminRoutes); and /api/upload adminKey check

🌟 آن لائن کمائی اب آسان 🌟
❌ کوئی ڈپازٹ نہیں
📲 ویڈیو دیکھیں، گیم کھیلیں، سروے کریں
👍 لائک، کمنٹ، سبسکرائب ٹاسک
💰 روزانہ کمائی کا موقع
🚀 ابھی مفت رجسٹر کریں
👇
https://www.yoursmed.xyz/?ref=EWDL9QDB