🚀 Live Demo: music-flow-vqmp.vercel.app
Full-stack music web app: React listener client, separate React admin panel, and an Express API with Socket.io, optional Redis, and MongoDB. A production-style music streaming app built from scratch — real-time listener counts via Socket.io, AI playlist generation, a loop-detection wellbeing system, and a full admin panel with live analytics. Built to understand how streaming products actually work under the hood.
Playlist apps are easy to mock in a UI. They are harder to get right when play counts matter, many tabs are open, admins need honest analytics, and you still want recommendations that do not hallucinate tracks that do not exist in your database. This repo is my answer: one API that serves two frontends, keeps counts consistent, and separates “LLM help” from “data the app actually owns.”
Listener (client/)
Sign up, log in, browse albums, play audio with a global player, like songs, build playlists, run an AI playlist generator (prompt in → query and rank from Mongo only), see recommendations plus a collaborative-filtering "People Also Listen To" feed, search the library, and—if enabled—get loop-detection nudges when the same song repeats a lot.
Admin (admin/)
JWT-protected dashboard: add/delete songs and albums (Cloudinary uploads), charts (aggregations), live-ish activity feed, loop-diagnosis stats.
API (server/)
30+ REST endpoints under /api/* with JWT auth, RBAC, Helmet.js security headers, and rate limiting. Socket.io for listener counts and realtime fan-out, optional Redis for caching and loop-session state with in-memory fallback, Cloudinary for media.
| Area | Choice | Why it is there |
|---|---|---|
| Listener UI | React 19, Vite, Tailwind | Fast dev, small bundle story with manual chunks in Vite |
| Admin UI | React 19, Vite, Tailwind | Same toolchain, isolated bundle and auth context |
| API | Node 20+, Express 5, ESM | One process for HTTP + Socket.io |
| Data | MongoDB + Mongoose | Flexible documents for songs, users, playlists, loop events |
| Cache | Redis (optional) | Song/listener caching; code paths tolerate Redis being off |
| Realtime | Socket.io | Listener counts + admin events on default namespace; /loopDiagnosis for interventions |
| Media | Cloudinary | Hosted audio + art |
| Auth | JWT + bcrypt, RBAC | Stateless API auth; admin routes gated by role |
| Security | Helmet.js, express-rate-limit | HTTP security headers; configurable /api rate limiting (on in production) |
| Ranking / AI | Collaborative filtering, optional .npy embedding hybrid, LLM intent parsing |
CF for the main feed; precomputed user/item vectors for /api/ai when embeddings are present; LLM parses playlist intent without inventing songs |
| Metric | Detail |
|---|---|
| Frontends | 2 separate React apps (listener + admin) |
| API endpoints | 30+ REST routes across auth, songs, albums, playlists, AI, loop-diagnosis |
| API security | Helmet.js headers + configurable /api rate limiting (enabled in production) |
| Real-time | Socket.io default namespace + /loopDiagnosis; admin updates via rooms |
| Auth system | JWT + bcrypt, role-based (user vs admin), multi-tab sync |
| AI feature | LLM intent parser → MongoDB query → ranked results (no hallucinated songs) |
| Recommendation engine | Collaborative filtering — weighted taste profile, versioned cache keys, CF fallback for cold-start users |
| Loop-detection system | Separate /loopDiagnosis Socket.IO namespace, Redis-backed session counters, admin diagnosis panel |
| Wellbeing system | Loop-detection that nudges users when same song repeats excessively |
| Test coverage | Server unit tests + loop-diagnosis suite + client/admin CI builds |
MusicFlow/
├── client/ # Listener React app (Vite dev server, default port 5000)
├── server/ # Express + Socket.io (`server.js`, `src/`, `tests/`)
├── admin/ # Admin React app (Vite, default port 5173)
├── docs/ # Long-form technical write-up
│ └── FINAL_PROJECT_GUIDE.md
├── data/ # Optional interaction export for embedding pipeline (`data/data.json`)
├── tests/ # Placeholder for future cross-package tests (API tests live in `server/tests/`)
├── screenshots/ # Drop UI captures here for your portfolio README previews
├── scripts/ # Repo-level helpers (loop sim, hooks, etc.)
├── .github/workflows/ # CI: tests + lint + build + audits
├── .env.example # Copy to repo root `.env` for local API
└── README.md # You are here
┌─────────────┐ ┌─────────────┐
│ client │ │ admin │
│ (Vite :5000)│ │ (Vite :5173)│
└──────┬──────┘ └──────┬──────┘
│ proxy /api │ proxy /api
└─────────┬─────────┘
▼
┌───────────────┐
│ server │ HTTP + Socket.io (:4002)
└───────┬───────┘
│
┌───────────┼───────────┐
▼ ▼ ▼
MongoDB Redis Cloudinary
(required) (optional) (media CDN)
- Vite in both frontends proxies
/apiand/socket.ioto the API in development so you rarely need CORS gymnastics locally. - Production can serve the built listener SPA from
server/client-dist/afternpm run build:client, or setFRONTEND_DISTto any folder that containsindex.html.
Deep dive (routes, models, edge cases, interview notes): docs/FINAL_PROJECT_GUIDE.md.
Home screen
AI playlist generation (GIF)
Overview (GIF)
Analytics dashboard
Loop diagnosis panel
Prerequisites: Node.js 20+, MongoDB URI, Cloudinary account, optional Redis.
-
Environment
cp .env.example .env
Fill at least
MONGODB_URI,JWT_SECRET, and theCLOUDINARY_*fields. Redis is optional; setREDIS_ENABLED=falseif you skip it. -
Client env (optional)
cp client/.env.example client/.env
Leave
VITE_API_URLempty when using the Vite proxy. -
Install and run (three terminals)
cd server && npm install && npm run server
cd client && npm install && npm run dev
cd admin && npm install && npm run dev
API default:
http://localhost:4002. Client dev:http://localhost:5000. Admin dev:http://localhost:5173.
JSON REST under /api/auth, /api/song, /api/album, /api/playlist, /api/admin, /api/ai, /api/loop-diagnosis, etc. Controllers own validation and side effects (play dedupe, like counters, playlist CRUD).
- Register/login return a JWT. The client stores it in
localStorage, setsAuthorizationon axios, and listens forstorageevents so multiple tabs stay in sync. - Admin uses the same token shape but separate layout and route guards;
role: adminis required for destructive catalog routes. - Socket.io reads the token from
handshake.auth, query, orAuthorizationheader, verifies it withJWT_SECRET, and joins per-user oradminrooms for targeted emits.
When enabled, Redis backs cache helpers and loop-diagnosis counters/session keys. If Redis is down, the code falls back to in-memory structures where it can so local development still works.
Used for live listener counts, pushing recent listening events, admin activity, and the /loopDiagnosis namespace for wellbeing-style interventions. The server deduplicates socket ↔ user mappings and can mirror “who is listening” in Redis when available.
Playback sessions are explicitly cleaned up on logout, token removal in another tab, socket disconnect, and song stop events. The client does not restore a global currentTrack from localStorage, so a later login or a different account cannot inherit stale playback state. The realtime server tracks active playback by socket and session and exposes admin-protected GET /api/realtime/diagnostics for development-time inspection.
LLM usage (playlists) — POST /api/playlist/generate uses OpenRouter to interpret prompt intent and always pulls song IDs from MongoDB (never LLM output).
When LLM_PROVIDER=openrouter, the backend hits the OpenRouter Chat Completions API at https://openrouter.ai/api/v1.
Authenticated recommendation UI is driven by GET /api/recommendations/cf/:userId. The collaborative-filtering service builds a weighted taste profile from likes and recently played songs, reinforces dominant language/taste clusters, and only uses trending fallback when CF candidates are insufficient.
The older authenticated /api/song/recommendations route is kept backward compatible by routing to the same CF service. Likes, unlikes, and recently played updates invalidate both legacy recommendation cache keys and versioned CF cache keys, so recommendation identity updates immediately after playback or feedback.
Optional embedding layer (/api/ai) — Offline-exported interaction data (data/data.json) can be turned into user_emb.npy / item_emb.npy via server/scripts/generate-embeddings.mjs. At runtime, embeddingRecommender.js loads those float32 matrices and ranks songs by cosine similarity, then blends in short-term feedback (likes/skips/plays) and adaptive profiles retrained from live signals. If the .npy files are missing, the AI route falls back to the same heuristics/CF paths so the API still responds.
cd server && npm test
cd server && npm run test:ld
cd client && npm run ci
cd admin && npm run ciGitHub Actions (.github/workflows/quality-gates.yml): server tests + high-severity audit, client and admin lint/build/audit, then npm run build:client to prove the SPA copy step works.
- Approximate test count: server unit tests + loop-diagnosis suite
- (~30+ assertions); CI runs on every push/PR.
- Play count spam: solved with a short server-side dedupe window and guarding concurrent play handlers so double-clicks do not inflate counts.
- Context re-renders in the player: keeping heavy playback state out of React context fields that change every frame took a few iterations; the write-up in
docs/FINAL_PROJECT_GUIDE.mdcalls out the approach. - Redis present but not guaranteed: every feature that likes Redis has a boring fallback so
npm run serverstill works on a laptop without Docker. - Recommendation cold start: New users have no history so CF returns no candidates. Solved with a trending-based fallback that activates only when CF candidates fall below a minimum threshold, then phases out as the user builds a taste profile.
- Windows + Node’s test runner: passing a directory to
node --testwas flaky here, so the server usesscripts/run-node-tests.mjsto expand test files explicitly—same behavior on Linux CI.
- Treat LLMs as planners, not databases: keep retrieval in code you control.
- Socket auth deserves the same seriousness as REST auth if you emit user-specific data.
- Monorepo-ish layout pays off when one
build:clientstep has to land static files beside the API for a single deploy target.
- E2E tests with Playwright covering auth, play, admin upload, and full recommendation flow
- httpOnly cookie sessions instead of localStorage JWT — reduces XSS attack surface
- Typed OpenAPI spec generated from routes for frontend type safety
- Offline playback queue using Service Workers
- Proper pagination on the recommendations feed for large song libraries
Maintained by MOHD IBADULLAH · GitHub Profile · LinkedIn
ISC





