A production-grade, scalable REST API built with the MERN Stack (MongoDB · Express · React · Node.js) to power a YouTube-like video hosting platform — featuring JWT auth, Cloudinary media management, aggregation pipelines, and more.
Author: Mohammad Asfin | Stack: MERN | License: MIT | Version: 1.0.0
🚀 Quick Start · 📬 API Docs · 🤝 Contributing · 🐛 Report Bug · 💡 Request Feature
This is the backend (M·E·N layer) of a full-stack MERN video hosting web application — similar to YouTube. It exposes a complete REST API consumed by a React frontend, handling everything from user authentication to video streaming metadata, social interactions, and cloud media management.
🧩 MERN Stack Breakdown:
Layer Technology Role M MongoDB + Mongoose Database & ODM E Express.js REST API Framework R React.js (Frontend — separate repo) Client-side UI N Node.js JavaScript Runtime
- 🔐 JWT Authentication — Stateless access & refresh token strategy
- 🔑 Password Security — bcrypt hashing (never stored as plain text)
- 📹 Video Uploads — Cloud media handling via Cloudinary + Multer
- 💬 Threaded Comments — Nested reply system for video comments
- 👍 Likes / Dislikes — On videos, comments, and tweets
- 📋 Playlists — Create, update, and manage video playlists
- 🔔 Subscriptions — Channel subscribe / unsubscribe with counts
- 📊 Dashboard — Channel analytics (views, subscribers, videos, likes)
- 🐦 Tweets & Polls — Short-form posts with optional images or interactive polls
- 🔔 Real-Time Notifications — Event-driven user alerts powered by Socket.io
- 🛡️ Report System — Flag inappropriate content (videos, comments, tweets, or users)
- 🩺 Health Check —
/healthcheckendpoint for uptime monitoring - 🚀 Rate Limiting — DDoS protection & spam prevention for endpoints
- 🌐 CORS — Fully configurable cross-origin resource sharing
- 📄 Pagination — MongoDB aggregation pipeline pagination
- 🧹 Code Quality — Prettier enforced across the codebase
📦 Video-Hosting-Backend-API
├── 📁 public/
│ └── 📁 temp/ # Temporary local file storage
├── 📁 src/
│ ├── 📁 controllers/ # Route handlers & business logic
│ │ ├── comment.controller.js
│ │ ├── dashboard.controller.js
│ │ ├── healthcheck.controller.js
│ │ ├── like.controller.js
│ │ ├── notification.controller.js
│ │ ├── playlist.controller.js
│ │ ├── report.controller.js
│ │ ├── subscription.controller.js
│ │ ├── tweet.controller.js
│ │ ├── user.controller.js
│ │ └── video.controller.js
│ ├── 📁 db/
│ │ └── index.js # MongoDB connection setup
│ ├── 📁 middlewares/
│ │ ├── auth.middleware.js # JWT verification middleware
│ │ ├── multer.middleware.js # File upload middleware
│ │ └── rateLimiter.js # API rate-limiting middleware
│ ├── 📁 models/ # Mongoose schemas & models
│ │ ├── comment.model.js
│ │ ├── like.model.js
│ │ ├── notification.model.js
│ │ ├── playlist.model.js
│ │ ├── report.model.js
│ │ ├── subscription.model.js
│ │ ├── tweet.model.js
│ │ ├── user.model.js
│ │ ├── video.model.js
│ │ └── view.model.js
│ ├── 📁 routes/ # Express route definitions
│ │ ├── comment.routes.js
│ │ ├── dashboard.routes.js
│ │ ├── healthcheck.routes.js
│ │ ├── like.routes.js
│ │ ├── notification.routes.js
│ │ ├── playlist.routes.js
│ │ ├── report.routes.js
│ │ ├── subscription.routes.js
│ │ ├── tweet.routes.js
│ │ ├── user.routes.js
│ │ └── video.routes.js
│ ├── 📁 utils/ # Reusable utility helpers
│ │ ├── ApiError.js # Custom error class
│ │ ├── ApiResponse.js # Standardised API response
│ │ ├── asyncHandler.js # Async error wrapper
│ │ └── cloudinary.js # Cloudinary upload helper
│ ├── app.js # Express app & middleware setup
│ ├── constants.js # App-wide constants (DB name, etc.)
│ ├── index.js # Server entry point
│ └── socket.js # Real-time WebSocket connection setup
├── .env.sample # Environment variable template
├── .gitignore
├── .prettierrc
├── .prettierignore
├── CONTRIBUTING.md
├── LICENSE
├── package.json
└── Readme.md
🔗 View Full ER Diagram on Eraser.io
In this project, we extensively use MongoDB Aggregation Pipelines to perform complex data retrieval and transformation operations directly within the database.
An aggregation pipeline consists of one or more "stages" that process documents. Each stage performs an operation on the input documents (e.g., filtering, grouping, calculating, or joining data) and passes the transformed documents to the next stage. It's highly efficient because the heavy processing happens on the database server rather than in the Node.js backend.
Here are some of the most critical aggregation operators ($) we utilize, especially in controllers like getUserChannelProfile and getWatchHistory:
$match: Acts like a standard query filter. It filters the document stream to allow only matching documents to pass into the next pipeline stage. We often use this as the very first stage to narrow down the dataset (e.g., finding a user by theirusernameor_id).$lookup: Performs a "left outer join" with another collection. This is crucial for a relational-like data structure in NoSQL. For example, when fetching a video, we use$lookupto join theuserscollection to get the video owner's avatar and username. We also use it to fetch a channel's subscribers from thesubscriptionscollection.$addFields: Adds new fields to documents or overwrites existing ones. We use this to compute new properties on the fly. For instance, calculatingsubscribersCountby finding the length of the joined array.$size: Returns the number of elements in an array. Commonly used alongside$addFieldsto count things like total likes, total videos, or total subscribers.$cond: A ternary operator that evaluates a boolean condition and returns one of two values depending on the result (if-then-else logic). We use this to determine if the currently logged-in user is subscribed to a channel (e.g., checking ifreq.user._idexists in the subscribers list and returningtrueorfalse).$project: Reshapes each document in the stream. It can include, exclude, or add new fields. We use$projectat the end of our pipelines to clean up the data and ensure we only send the necessary fields to the frontend, stripping out unnecessary nested arrays or sensitive data.
| Technology | Version | Purpose |
|---|---|---|
| Node.js | 18+ | JavaScript runtime |
| Express.js | ^4.x | REST API framework |
| Socket.io | ^4.x | Real-time WebSocket connection |
| MongoDB | Atlas | NoSQL document database |
| Mongoose | ^8.x | MongoDB ODM |
| mongoose-aggregate-paginate-v2 | ^1.x | Aggregation pagination plugin |
| jsonwebtoken | ^9.x | JWT access & refresh tokens |
| bcrypt | ^5.x | Secure password hashing |
| express-rate-limit | ^8.x | API rate limiting / DDoS protection |
| Cloudinary | ^1.x | Cloud media upload & management |
| Multer | ^1.x | Multipart file upload middleware |
| dotenv | ^16.x | Environment variable loader |
| cookie-parser | ^1.x | HTTP cookie parsing |
| cors | ^2.x | Cross-Origin Resource Sharing |
| nodemon (dev) | ^3.x | Auto-restart on file changes |
| Prettier (dev) | ^3.x | Opinionated code formatter |
Ensure the following are installed before proceeding:
- ✅ Node.js v18+
- ✅ Git
- ✅ MongoDB Atlas account
- ✅ Cloudinary account
git clone https://github.com/Mohammad-Asfin/Video-Hosting-Backend-API.git
cd Video-Hosting-Backend-APIIndividual packages (with descriptions):
# Dev — Auto-restart on file changes
npm i -D nodemon
# Dev — Opinionated code formatter
npm i -D prettier
# MongoDB object modeling (ODM)
npm i mongoose
# Fast, minimalist web framework for Node.js
npm i express
# Load environment variables from .env file
npm i dotenv
# HTTP cookie parsing middleware
npm i cookie-parser
# Node.js CORS middleware for Express
npm i cors
# Mongoose aggregation pipeline pagination plugin
npm i mongoose-aggregate-paginate-v2
# Secure password hashing library
npm i bcrypt
# JSON Web Token implementation
npm i jsonwebtoken
# Cloudinary Node SDK — media upload & transformation
npm i cloudinary
# Multipart/form-data middleware (file uploads)
npm i multer
# Real-time WebSocket support for Node.js
npm i socket.io
# Basic rate-limiting middleware for Express
npm i express-rate-limitOr install everything at once:
npm installcp .env.sample .envOpen .env and fill in your values:
PORT=8000
MONGODB_URI=mongodb+srv://<username>:<password>@cluster0.xxxxx.mongodb.net
CORS_ORIGIN=*
# ⚠️ Use DIFFERENT secrets for access and refresh tokens
# Generate with: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
ACCESS_TOKEN_SECRET=<your_64_byte_hex_secret_here>
ACCESS_TOKEN_EXPIRY=1d
REFRESH_TOKEN_SECRET=<your_different_64_byte_hex_secret_here>
REFRESH_TOKEN_EXPIRY=10d
# Cloudinary (https://cloudinary.com/console)
CLOUDINARY_CLOUD_NAME=<your_cloud_name>
CLOUDINARY_API_KEY=<your_api_key>
CLOUDINARY_API_SECRET=<your_api_secret>Generate secure secrets:
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"Run this command twice — use one output for ACCESS_TOKEN_SECRET and a different one for REFRESH_TOKEN_SECRET.
⚠️ Security Rules:
- Never use the same secret for both tokens
- Use at least 64 random bytes (128 hex characters)
- NEVER commit
.envto GitHub — it's already in.gitignore
# Development (nodemon auto-restarts on changes)
npm run devServer running at → http://localhost:8000
http://localhost:8000/api/v1
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/register |
Register a new user | ❌ |
POST |
/login |
Login & receive tokens | ❌ |
POST |
/logout |
Logout current user | ✅ |
POST |
/refresh-token |
Refresh access token | ❌ |
PATCH |
/change-password |
Change current password | ✅ |
GET |
/current-user |
Get logged-in user profile | ✅ |
PATCH |
/update-account |
Update name & email | ✅ |
PATCH |
/avatar |
Upload new avatar | ✅ |
PATCH |
/cover-image |
Upload new cover image | ✅ |
GET |
/c/:username |
Get channel profile | ✅ |
GET |
/history |
Get watch history | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/ |
Get all videos | ❌ |
POST |
/ |
Publish a video | ✅ |
GET |
/:videoId |
Get video by ID | ✅ |
PATCH |
/:videoId |
Update video details | ✅ |
DELETE |
/:videoId |
Delete a video | ✅ |
PATCH |
/toggle/publish/:videoId |
Toggle publish status | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/:videoId |
Get all comments for a video | ✅ |
POST |
/:videoId |
Add a comment to a video (supports rate limit) | ✅ |
PATCH |
/c/:commentId |
Update a comment | ✅ |
DELETE |
/c/:commentId |
Delete a comment | ✅ |
GET |
/c/:commentId/replies |
Get nested replies for a comment | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/toggle/v/:videoId |
Toggle like on video | ✅ |
POST |
/toggle/c/:commentId |
Toggle like on comment | ✅ |
POST |
/toggle/t/:tweetId |
Toggle like on tweet | ✅ |
GET |
/videos |
Get all liked videos | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/ |
Create a playlist | ✅ |
GET |
/user/:userId |
Get user playlists | ✅ |
GET |
/:playlistId |
Get playlist by ID | ✅ |
PATCH |
/:playlistId |
Update playlist | ✅ |
DELETE |
/:playlistId |
Delete playlist | ✅ |
PATCH |
/add/:videoId/:playlistId |
Add video to playlist | ✅ |
PATCH |
/remove/:videoId/:playlistId |
Remove video from playlist | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/c/:channelId |
Toggle subscription | ✅ |
GET |
/c/:channelId |
Get channel subscribers | ✅ |
GET |
/u/:subscriberId |
Get subscribed channels | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/ |
Create tweet (supports content + optional image + optional poll options) | ✅ |
GET |
/user/:userId |
Get user tweets | ✅ |
PATCH |
/:tweetId |
Update tweet | ✅ |
DELETE |
/:tweetId |
Delete tweet | ✅ |
PATCH |
/:tweetId/vote |
Vote on a poll option | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/ |
Get all notifications for logged-in user | ✅ |
PATCH |
/:notificationId/read |
Mark a notification as read | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
POST |
/ |
Create/submit a content report | ✅ |
GET |
/ |
Get all reports (Admin/Moderator view) | ✅ |
PATCH |
/:reportId |
Resolve or dismiss a report | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/stats |
Get channel stats | ✅ |
GET |
/videos |
Get channel videos | ✅ |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
GET |
/ |
Check API health | ❌ |
This API relies on MongoDB Atlas for database storage and Cloudinary for media management. Here is a standard flow for testing authentication and media uploads using Postman:
💡 Postman Tip: To make testing easier, set up a Collection Variable named
Video Hostingwith the valuehttp://localhost:8000/api/v1. You can then use{{Video Hosting}}in your URLs.
- Method:
POST - URL:
{{Video Hosting}}/users/register - Body Type:
form-data
| Key | Type | Example Value | Description |
|---|---|---|---|
fullName |
Text | Levi Ackerman |
User's full name |
username |
Text | leviackerman |
Unique username |
email |
Text | levi@aot.com |
User's email |
password |
Text | 12345678 |
Secure password |
avatar |
File | Levi.jpg |
Required. Uploads to Cloudinary. |
coverImage |
File | AOT cover.jpg |
Optional. Uploads to Cloudinary. |
🔄 Data Flow:
- Cloudinary: The
avatarandcoverImagefiles are processed by Multer and successfully uploaded to your Cloudinary Media Library. The secure URLs are returned.- MongoDB Atlas: A new document is created in the
userscollection within yourvideotubedatabase, securely storing the hashed password and the Cloudinary image URLs.
Expected response: 201 Created
- Method:
POST - URL:
{{Video Hosting}}/users/login - Body Type:
raw(JSON)
{
"email": "levi@aot.com",
"password": "12345678"
}Expected Response: 200 OK
The server validates the credentials and returns an accessToken (for authorizing requests) and a refreshToken (for getting new access tokens). These tokens are also securely set in HttpOnly cookies.
- Method:
GET - URL:
{{Video Hosting}}/users/current-user - Headers: Requires the user to be logged in (cookies set by login).
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"_id": "6a5cb07f4822053c999663b8",
"username": "leviackerman",
"email": "levi@aot.com",
"fullName": "Levi Ackerman",
"avatar": "http://res.cloudinary.com/...",
"coverImage": "http://res.cloudinary.com/...",
"watchHistory": [],
"createdAt": "2026-07-19T11:09:51.446Z",
"updatedAt": "2026-07-21T09:09:51.918Z",
"__v": 0
},
"message": "User fetched successfully",
"success": true
}- Method:
GET - URL:
{{Video Hosting}}/users/c/leviackerman - Headers: Requires the user to be logged in (cookies set by login).
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"_id": "6a5cb07f4822053c999663b8",
"username": "leviackerman",
"email": "levi@aot.com",
"fullName": "Levi Ackerman",
"avatar": "http://res.cloudinary.com/...",
"coverImage": "http://res.cloudinary.com/...",
"subscribersCount": 0,
"channelsSubscribedToCount": 0,
"isSubscribed": false
},
"message": "User channel fetched successfully",
"success": true
}- Method:
GET - URL:
{{Video Hosting}}/users/history - Headers: Requires the user to be logged in (cookies set by login).
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": [],
"message": "Watch history fetched successfully",
"success": true
}- Method:
POST - URL:
{{Video Hosting}}/users/change-password - Headers: Requires the user to be logged in (cookies set by login).
- Body Type:
raw(JSON)
{
"oldPassword": "123456789",
"newPassword": "12345678"
}Expected Response: 200 OK
{
"statusCode": 200,
"data": {},
"message": "Password changed successfully",
"success": true
}- Method:
POST - URL:
{{Video Hosting}}/users/refresh-token - Headers: Requires the refresh token cookie.
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
},
"message": "Access token refreshed",
"success": true
}- Method:
POST - URL:
{{Video Hosting}}/users/logout - Headers: Requires the user to be logged in (cookies set by login).
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {},
"message": "User logged Out",
"success": true
}- Method:
POST - URL:
{{Video Hosting}}/videos - Headers: Requires the user to be logged in (cookies set by login).
- Body Type:
form-data
| Key | Type | Example Value | Description |
|---|---|---|---|
title |
Text | My First Vlog |
Video title |
description |
Text | A day in my life |
Video description |
videoFile |
File | vlog.mp4 |
Required. Uploads to Cloudinary. |
thumbnail |
File | thumbnail.jpg |
Required. Uploads to Cloudinary. |
Expected Response: 201 Created
{
"statusCode": 201,
"data": {
"videoFile": "http://res.cloudinary.com/...",
"thumbnail": "http://res.cloudinary.com/...",
"title": "My First Vlog",
"description": "A day in my life"
},
"message": "Video published successfully",
"success": true
}- Method:
POST - URL:
{{Video Hosting}}/comments/:videoId - Headers: Requires the user to be logged in.
- Body Type:
raw(JSON)
{
"content": "This is an amazing video!"
}Expected Response: 201 Created
- Method:
POST - URL:
{{Video Hosting}}/likes/toggle/v/:videoId(Replace/v/with/c/or/t/for comments/tweets) - Headers: Requires the user to be logged in.
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"liked": true
},
"message": "Like added to video",
"success": true
}- Method:
POST - URL:
{{Video Hosting}}/playlist - Headers: Requires the user to be logged in.
- Body Type:
raw(JSON)
{
"name": "React Tutorials",
"description": "Best tutorials to learn React.js"
}Expected Response: 201 Created
- Method:
POST - URL:
{{Video Hosting}}/subscriptions/c/:channelId - Headers: Requires the user to be logged in.
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"subscribed": true
},
"message": "Subscribed successfully",
"success": true
}- Method:
POST - URL:
{{Video Hosting}}/tweets - Headers: Requires the user to be logged in.
- Body Type:
raw(JSON)
{
"content": "Just launched my new channel!"
}Expected Response: 201 Created
- Method:
GET - URL:
{{Video Hosting}}/dashboard/stats - Headers: Requires the user to be logged in.
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"totalSubscribers": 1500,
"totalVideos": 25,
"totalViews": 50000,
"totalLikes": 1200
},
"message": "Channel stats fetched successfully",
"success": true
}- Method:
GET - URL:
{{Video Hosting}}/healthcheck - Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {},
"message": "OK",
"success": true
}- Method:
GET - URL:
{{Video Hosting}}/videos?page=1&limit=10&sortBy=createdAt&sortType=desc - Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"docs": [
{
"_id": "6a604e404b74605254976f7d",
"videoFile": "http://res.cloudinary.com/...",
"thumbnail": "http://res.cloudinary.com/...",
"title": "My First Vlog",
"description": "A day in my life",
"duration": 19.64,
"views": 0,
"isPublished": true,
"owner": {
"_id": "6a5cb07f4822053c999663b8",
"username": "leviackerman",
"avatar": "http://res.cloudinary.com/..."
}
}
],
"totalDocs": 1,
"limit": 10,
"page": 1,
"totalPages": 1,
"pagingCounter": 1,
"hasPrevPage": false,
"hasNextPage": false,
"prevPage": null,
"nextPage": null
},
"message": "Videos fetched successfully",
"success": true
}- Method:
GET - URL:
{{Video Hosting}}/videos/:videoId - Headers: Requires the user to be logged in (to add to history).
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"_id": "6a604e404b74605254976f7d",
"videoFile": "http://res.cloudinary.com/...",
"thumbnail": "http://res.cloudinary.com/...",
"title": "My First Vlog",
"description": "A day in my life",
"duration": 19.64,
"views": 1,
"isPublished": true,
"owner": {
"_id": "6a5cb07f4822053c999663b8",
"username": "leviackerman",
"avatar": "http://res.cloudinary.com/..."
},
"likesCount": 10,
"isLiked": true
},
"message": "Video fetched successfully",
"success": true
}- Method:
PATCH - URL:
{{Video Hosting}}/users/avatar - Headers: Requires the user to be logged in.
- Body Type:
form-data
| Key | Type | Example Value | Description |
|---|---|---|---|
avatar |
File | new_avatar.jpg |
Required. Uploads to Cloudinary. |
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"_id": "6a5cb07f4822053c999663b8",
"username": "leviackerman",
"avatar": "http://res.cloudinary.com/.../new_avatar.jpg",
"coverImage": "http://res.cloudinary.com/..."
},
"message": "Avatar image updated successfully",
"success": true
}- Method:
PATCH - URL:
{{Video Hosting}}/playlist/add/:videoId/:playlistId - Headers: Requires the user to be logged in.
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": {
"_id": "6a6053954b74605254976f8e",
"name": "React Tutorials",
"videos": ["6a604e404b74605254976f7d"]
},
"message": "Video added to playlist successfully",
"success": true
}- Method:
GET - URL:
{{Video Hosting}}/likes/videos - Headers: Requires the user to be logged in.
- Body: None
Expected Response: 200 OK
{
"statusCode": 200,
"data": [
{
"_id": "6a6057c74b74605254976f9d",
"video": {
"_id": "6a604e404b74605254976f7d",
"videoFile": "http://res.cloudinary.com/...",
"thumbnail": "http://res.cloudinary.com/...",
"title": "My First Vlog",
"duration": 19.64,
"views": 1,
"ownerDetails": {
"username": "leviackerman",
"avatar": "http://res.cloudinary.com/..."
}
}
}
],
"message": "Liked videos fetched successfully",
"success": true
}To test this API locally or in production using Postman, follow these configuration guidelines:
Create a new Postman environment and define the following variables:
base_url:http://localhost:8000/api/v1
The API uses HTTP-only cookies (accessToken and refreshToken) for authentication:
- Automatic Handling: Postman handles cookies automatically. When you send a successful
POST /users/loginrequest, the server returns the tokens in theSet-Cookieheader. Subsequent requests will include them automatically. - Manual Header Handling: If you prefer to pass tokens via headers, you can manually set the
Authorizationheader on protected endpoints:- Header:
Authorization - Value:
Bearer <your_access_token>
- Header:
For endpoints requiring file uploads (e.g., POST /users/register or POST /videos/):
- Change the body type in Postman to form-data.
- Use the exact field names defined in the routes:
- User Registration:
avatar(File),coverImage(File),username(Text),email(Text),fullName(Text),password(Text). - Video Upload:
videoFile(File),thumbnail(File),title(Text),description(Text).
- User Registration:
The diagram below illustrates how an HTTP request flows through the backend application architecture from the client to the database and back:
graph TD
A[Client / Postman] -->|HTTP Request| B[Express App / Entry Point]
B -->|Router Mapping| C[Express Router]
C -->|CORS & rateLimiter Middlewares| D[Security/Limit Middlewares]
D -->|verifyJWT & upload Middlewares| E[Auth & File Upload Middlewares]
E -->|Call Controller| F[Controller Handler]
F -->|Upload Media| G[Cloudinary Service]
F -->|Query / Mutate| H[Mongoose Models]
H -->|Read/Write| I[(MongoDB Database)]
F -->|ApiResponse / ApiError| A
- Client Request: The client sends an HTTP request targeting a specific resource route.
- Routing & Security: Express resolves the path. Security/rate limiter middlewares check request frequency and headers.
- Authentication & Uploads: Authentication middlewares (
verifyJWT) validate tokens. If a file is uploaded, Multer processes the file and attaches it toreq.fileorreq.files. - Controller Logic: The controller executes business logic, performs data validation, uploads media to Cloudinary, and queries MongoDB via Mongoose Models.
- Response Delivery: The controller wraps the results in a standardized
ApiResponse(or forwards errors usingApiError) and sends it back to the client.
{
"dev": "nodemon -r dotenv/config --experimental-json-modules src/index.js"
}We welcome contributions! Please read CONTRIBUTING.md for full guidelines.
Quick steps:
- 🍴 Fork the repository
- 🌿 Create a branch:
git checkout -b feature/your-feature - ✅ All core controller TODOs have been completed! You can add your own features here.
- 💅 Format code:
npx prettier --write . - 📬 Open a Pull Request against
main - 🔍 After review, your repo link gets added to this README
Open issues at: GitHub Issues
If you discover a security vulnerability, please do NOT open a public issue.
Instead, contact the maintainer directly via GitHub.
- Keep your
.envfile private — never commit it - Rotate JWT secrets regularly in production
- Use environment-specific Cloudinary API keys
This project is licensed under the MIT License.
See the LICENSE file for full details.
MIT License — Copyright (c) 2026 Mohammad Asfin
| Resource | Link |
|---|---|
| 🔗 ER Diagram | Eraser.io Model |
| 🌐 GitHub Repository | Video-Hosting-Backend-API |
| 🤝 Contributing Guide | CONTRIBUTING.md |
| ☁️ Cloudinary | cloudinary.com |
| 🍃 MongoDB Atlas | mongodb.com/cloud/atlas |
| 📗 Express Docs | expressjs.com |
| 🟢 Node.js | nodejs.org |
Built with ❤️ using the MERN Stack — MongoDB · Express · React · Node.js
⭐ Star this repo if you found it helpful!