StreamCore is a fully-featured REST API powering a video-sharing platform. It handles everything a modern video platform needs : user identity, media uploads, subscriptions, engagement (likes/comments), AI-assisted discovery, and creator analytics.
This isn't a tutorial clone. It's built with production patterns:
- Dual-token JWT auth with rotation + server-side revocation
- Cloudinary CDN for media (images + videos) : zero binary data in MongoDB
- MongoDB Aggregation Pipelines for complex relational queries without a SQL database
- Gemini AI for comment moderation, semantic search expansion, and auto-SEO tagging
- Tiered rate limiting : global, auth-specific, and AI-specific limits
- Content-based video recommendations from watch history : no external ML service
| Domain | Capabilities |
|---|---|
| Auth | Register, Login, Logout, Refresh Token, Password Change |
| Users | Profile management, Avatar/Cover upload, Watch History |
| Videos | Upload, Publish/Unpublish toggle, Paginated feed, Recommendations |
| Engagement | Like/Unlike videos, comments & tweets : polymorphic model |
| Comments | CRUD with AI content moderation before save |
| Subscriptions | Subscribe/Unsubscribe, Subscriber list, Subscribed channels |
| Playlists | Create, manage, add/remove videos |
| Creator Dashboard | Channel stats : total views, subscribers, videos, likes |
| AI Features | Semantic search expansion, auto-tag generation, description generator |
| Search | Tag-based + AI-expanded full-text search with watch history recommendations |
┌─────────────────────────────────────────────────────────────────┐
│ CLIENT │
│ (Browser / Mobile / Postman) │
└───────────────────────────┬─────────────────────────────────────┘
│ HTTPS
▼
┌─────────────────────────────────────────────────────────────────┐
│ EXPRESS SERVER │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ MIDDLEWARE CHAIN │ │
│ │ Rate Limiter → CORS → Body Parser → Cookie Parser │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ ROUTER LAYER │ │
│ │ /users /videos /comments /likes /subscriptions │ │
│ │ /playlists /dashboard /ai /tweets /healthcheck │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────┐ ┌───────────┴──────────┐ ┌──────────────────┐ │
│ │ verifyJWT│ │ CONTROLLERS │ │ multer (upload) │ │
│ │middleware│ │ Business Logic │ │ middleware │ │
│ └──────────┘ └──────────┬───────────┘ └──────────────────┘ │
└──────────────────────────┬──────────────────────────────────────┘
│
┌───────────────┼────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌──────────┐ ┌──────────────┐
│ MongoDB │ │Cloudinary│ │ Gemini AI │
│ Atlas │ │ CDN │ │ (Google) │
│ │ │ │ │ │
│ Users │ │ Avatars │ │ Moderation │
│ Videos │ │ Covers │ │ Tags │
│ Comments │ │ Videos │ │ Search │
│ Likes │ │ Thumbs │ │ Descriptions│
│ Subs │ └──────────┘ └──────────────┘
│ Playlists │
└─────────────┘
Every request passes through this exact sequence:
Incoming Request
│
├─► [1] generalRateLimiter (200 req / 15 min per IP)
├─► [2] cors() (validates Origin header)
├─► [3] express.json() (parses JSON body)
├─► [4] express.urlencoded() (parses form data)
└─► [5] cookieParser() (parses req.cookies)
│
┌──────────┴──────────┐
│ Route-level MW │
│ verifyJWT │ → decodes JWT → attaches req.user
│ multer.upload() │ → saves file to /public/temp
└──────────┬──────────┘
│
Controller
│
┌──────┴──────┐
│ Success │ Error
▼ ▼
ApiResponse ApiError → global error handler
res.json() res.status(code).json({ message })
Register
| Step | Action |
|---|---|
| 1 | Validate fields + password strength |
| 2 | Upload avatar to Cloudinary |
| 3 | Hash password with bcrypt (10 rounds) |
| 4 | Save user to MongoDB |
| 5 | Return 201 Created |
Login
| Step | Action |
|---|---|
| 1 | Find user by username or email |
| 2 | Verify password with bcrypt.compare() |
| 3 | Generate access token (1d) + refresh token (10d) |
| 4 | Store refresh token in MongoDB |
| 5 | Set both tokens as httpOnly + Secure cookies |
| 6 | Return user object + tokens in response body |
Authenticated Request
| Step | Action |
|---|---|
| 1 | Extract token from cookie or Authorization header |
| 2 | Verify with jwt.verify() using ACCESS_TOKEN_SECRET |
| 3 | Fetch user from DB, exclude password and refreshToken |
| 4 | Attach to req.user : available in all downstream controllers |
Token Refresh
| Step | Action |
|---|---|
| 1 | Extract refresh token from cookie or request body |
| 2 | Verify signature with REFRESH_TOKEN_SECRET |
| 3 | Compare against token stored in MongoDB (rotation check) |
| 4 | Generate new access + refresh token pair |
| 5 | Update MongoDB with new refresh token |
| 6 | Set new cookies, return new tokens |
Logout
| Step | Action |
|---|---|
| 1 | Remove refresh token from MongoDB via $unset |
| 2 | Clear both cookies from browser |
| 3 | Access token remains valid until expiry (stateless trade-off) |
User ──────────────────────────────────────┐
│ _id, username, email, fullName │
│ avatar, coverImage (Cloudinary URLs) │
│ password (bcrypt), refreshToken │
│ watchHistory: [VideoId] │
└──────┬──────────────────────────────────-┘
│
│ 1:N M:N (via Subscription)
▼ ┌──────────────────────┐
Video │ subscriber: UserId │
│ _id, title, description │ channel: UserId │
│ videoFile, thumbnail (URLs) └──────────────────────┘
│ duration, views, isPublished
│ owner: UserId
│ tags: [String]
└──────┬────────────────────────
│
│ 1:N
▼
Comment
│ _id, content
│ video: VideoId
│ owner: UserId
└────────────────────────────
Like (Polymorphic)
│ video?: VideoId
│ comment?: CommentId
│ tweet?: TweetId
│ likedBy: UserId
Playlist
│ name, description
│ videos: [VideoId]
│ owner: UserId
professional-project/
├── src/
│ ├── index.js # Entry: DB connect → server start
│ ├── app.js # Express app, middleware, route mounts
│ ├── constants.js # DB_NAME constant
│ │
│ ├── db/
│ │ └── index.js # MongoDB connection (Mongoose)
│ │
│ ├── models/
│ │ ├── user.model.js # Schema + bcrypt hooks + JWT methods
│ │ ├── videos.model.js # Schema + aggregate-paginate plugin
│ │ ├── comment.model.js # Schema + aggregate-paginate plugin
│ │ ├── like.model.js # Polymorphic: video/comment/tweet
│ │ ├── subscription.model.js # subscriber ↔ channel junction
│ │ └── playlist.model.js # videos array + owner
│ │
│ ├── controllers/
│ │ ├── user.controller.js # Register, Login, Profile, History
│ │ ├── video.controller.js # Upload, CRUD, Feed, Recommendations
│ │ ├── comment.controller.js # CRUD + AI moderation
│ │ ├── like.controller.js # Toggle likes
│ │ ├── subscription.controller.js
│ │ ├── playlist.controller.js
│ │ ├── dashboard.controller.js # Channel analytics aggregation
│ │ └── ai.controller.js # Description generation endpoint
│ │
│ ├── routes/
│ │ ├── user.routes.js
│ │ ├── videos.routes.js
│ │ ├── comment.routes.js
│ │ ├── like.routes.js
│ │ ├── subscription.routes.js
│ │ ├── playlist.routes.js
│ │ ├── dashboard.routes.js
│ │ ├── ai.routes.js
│ │ ├── tweet.routes.js
│ │ └── healthcheck.routes.js
│ │
│ ├── middlewares/
│ │ ├── auth.middleware.js # JWT verification → req.user
│ │ └── multer.middleware.js # Multipart file handling → /public/temp
│ │
│ └── utils/
│ ├── asyncHandler.js # Async error wrapper for controllers
│ ├── ApiError.js # Custom error class (extends Error)
│ ├── ApiResponse.js # Standardized success envelope
│ ├── cloudinary.js # Upload helper
│ ├── deleteFromCloudinary.js # Delete helper
│ ├── aiService.js # Moderation, semantic search, tags
│ ├── gemini.js # Gemini API abstraction
│ └── validatePasswordStrength.js
│
├── public/
│ └── temp/ # Temporary file storage (pre-Cloudinary)
│
├── .env.example # Required environment variables
├── .gitignore
└── package.json
- Node.js v18+
- A MongoDB Atlas account (free tier works)
- A Cloudinary account (free tier works)
- A Google AI Studio API key (Gemini)
git clone https://github.com/priyansh00soni/streamcore-api.git
cd streamcore-api
npm installcp .env.example .envOpen .env and fill in every value:
PORT=8000
# MongoDB Atlas connection string
MONGO_URI=mongodb+srv://<username>:<password>@cluster0.xxxxxx.mongodb.net/streamcore?retryWrites=true&w=majority
# Set to your frontend URL in production. Never use * in production.
CORS_ORIGIN=http://localhost:3000
# Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
ACCESS_TOKEN_SECRET=your_32_byte_hex_secret
ACCESS_TOKEN_EXPIRY=1d
# Use a DIFFERENT secret from access token
REFRESH_TOKEN_SECRET=your_different_32_byte_hex_secret
REFRESH_TOKEN_EXPIRY=10d
# From your Cloudinary dashboard → Settings → Access Keys
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
# From Google AI Studio
GEMINI_API_KEY=your_gemini_api_keyTip: Generate strong secrets with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Run it twice : use one for ACCESS_TOKEN_SECRET, one for REFRESH_TOKEN_SECRET.
# Development (hot reload)
npm run dev
# Production
node src/index.jsServer starts at http://localhost:8000
curl http://localhost:8000/api/v1/healthcheck
# → { "statusCode": 200, "data": "OK", "message": "Health check passed", "success": true }Base URL: https://stream-core.onrender.com/api/v1
Local development:
http://localhost:8000/api/v1
All responses follow this envelope:
// Success
{ "statusCode": 200, "data": { ... }, "message": "...", "success": true }
// Error
{ "statusCode": 400, "message": "...", "success": false }| Method | Endpoint | Auth | Body / Params | Description |
|---|---|---|---|---|
POST |
/users/register |
❌ | fullName, email, username, password + avatar (file) + coverImage (file, optional) |
Register new user. Avatar required. |
POST |
/users/login |
❌ | username or email + password |
Login. Returns tokens in cookies + body. |
POST |
/users/logout |
✅ | - | Clears cookies, revokes refresh token in DB. |
POST |
/users/refresh-token |
❌ | refreshToken (cookie or body) |
Get new access + refresh tokens. |
PATCH |
/users/change-password |
✅ | oldPassword, newPassword |
Change password. Validates strength. |
GET |
/users/current-user |
✅ | - | Get logged-in user's full profile. |
PATCH |
/users/update-account-details |
✅ | fullName, email |
Update name and email. |
PATCH |
/users/update-avatar |
✅ | avatar (file) |
Replace avatar. Old file deleted from Cloudinary. |
PATCH |
/users/update-cover-image |
✅ | coverImage (file) |
Replace cover image. |
GET |
/users/channel/:username |
✅ | - | Get public channel profile with subscriber count + isSubscribed. |
GET |
/users/watch-history |
✅ | - | Get watch history with full video + owner details. |
| Method | Endpoint | Auth | Body / Params | Description |
|---|---|---|---|---|
GET |
/videos |
❌ | ?query, page, limit, sortBy, sortType, userId |
Paginated feed. With query: text search. Without: personalized recommendations from watch history. |
POST |
/videos/publish |
✅ | title, description + videoFile (file) + thumbnail (file) |
Upload video. Auto-generates tags via Gemini. |
GET |
/videos/:videoId |
Optional | - | Get video details. Increments views. Tracks watch history if authenticated. |
PATCH |
/videos/:videoId |
✅ | title, description + thumbnail (file, optional) |
Update video. Owner only. |
DELETE |
/videos/:videoId |
✅ | - | Delete video + Cloudinary files. Owner only. |
PATCH |
/videos/toggle-publish/:videoId |
✅ | - | Toggle public/private. Owner only. |
| Method | Endpoint | Auth | Body / Params | Description |
|---|---|---|---|---|
GET |
/comments/:videoId |
❌ | ?page, limit |
Get paginated comments for a video. |
POST |
/comments/:videoId |
✅ | content |
Add comment. Passes AI moderation before save. |
PATCH |
/comments/c/:commentId |
✅ | content |
Edit comment. Owner only. |
DELETE |
/comments/c/:commentId |
✅ | - | Delete comment. Owner only. |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
PATCH |
/likes/toggle/v/:videoId |
✅ | Toggle like on a video |
PATCH |
/likes/toggle/c/:commentId |
✅ | Toggle like on a comment |
PATCH |
/likes/toggle/t/:tweetId |
✅ | Toggle like on a tweet |
GET |
/likes/videos |
✅ | Get all videos liked by current user |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/subscriptions/c/:channelId |
✅ | Subscribe / Unsubscribe toggle |
GET |
/subscriptions/c |
✅ | Get channels current user subscribes to |
GET |
/subscriptions/u |
✅ | Get subscribers of current user's channel |
| Method | Endpoint | Auth | Body / Params | Description |
|---|---|---|---|---|
POST |
/playlists |
✅ | name, description |
Create playlist |
GET |
/playlists/user/:userId |
❌ | - | Get all playlists of a user |
PATCH |
/playlists/add/:playlistId/:videoId |
✅ | - | Add video to playlist |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/dashboard/stats |
✅ | Channel stats: total views, subscribers, videos, likes |
GET |
/dashboard/videos |
✅ | All videos of current channel with engagement metrics |
Rate limited: 5 requests / minute
| Method | Endpoint | Auth | Body | Description |
|---|---|---|---|---|
POST |
/ai/description |
✅ | title |
Generate a YouTube-style video description using Gemini |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/healthcheck |
❌ | Server + DB health status |
| Measure | Implementation |
|---|---|
| Password hashing | bcrypt with 10 salt rounds : one-way, rainbow-table resistant |
| Token storage | httpOnly + Secure cookies : inaccessible to JavaScript |
| Token rotation | Each refresh generates a new refresh token, invalidating the old |
| Rate limiting | 3-tier: global (200/15min), auth (10/1min), AI (5/1min) |
| CORS | Configurable origin whitelist via CORS_ORIGIN env variable |
| Authorization | Owner checks at DB level : findOne({ _id: id, owner: req.user._id }) |
| Secret exclusion | select('-password -refreshToken') on all user queries |
| Layer | Technology | Version | Why |
|---|---|---|---|
| Runtime | Node.js | v18+ | Non-blocking I/O, large ecosystem |
| Framework | Express | v5.2.1 | Minimal, flexible, industry standard |
| Database | MongoDB + Mongoose | v9.3.3 | Flexible schema, excellent for aggregation |
| Auth | jsonwebtoken + bcrypt | v9.0.3 / v6.0.0 | Industry-standard JWT + secure hashing |
| Media | Cloudinary | v2.9.0 | Managed CDN, no binary storage in DB |
| File upload | Multer | v2.1.1 | Multipart form handling |
| AI | @google/genai (Gemini) | v2.7.0 | Content moderation + semantic features |
| Rate limiting | express-rate-limit | v8.5.2 | Brute force + abuse prevention |
| Pagination | mongoose-aggregate-paginate-v2 | v1.1.4 | Cursor-based pagination on aggregates |
Import the collection into Postman to test all 30+ endpoints with pre-configured variables and example payloads.
Set the BASE_URL environment variable to https://stream-core.onrender.com/api/v1 for production or http://localhost:8000/api/v1 for local testing.
Quick manual test : register a user:
curl -X POST http://localhost:8000/api/v1/users/register \
-F "fullName=Test User" \
-F "email=test@example.com" \
-F "username=testuser" \
-F "password=Test@12345" \
-F "avatar=@/path/to/avatar.jpg"Priyansh Built as a production-grade backend project demonstrating real-world patterns in Node.js, MongoDB, and AI integration.
If this helped you : drop a ⭐ on the repo.