You Listen – My Private Spotify Alternative

Overview of the Application

You Listen is a private, full-stack music streaming platform built for a small user base.

  • Ad-free, clutter-free streaming.

  • Admin-controlled library → Only admins upload songs.

  • Signed URL streaming → Secure access, preventing unauthorized downloads.

  • Custom React audio player → Playlists, shuffle, song likes, and keyboard shortcuts.

This project was both a practical solution and a full-stack learning experience, combining Next.js, Express, BullMQ, and secure media streaming.


Idea Behind the Project

I wanted a personal music platform that:

  1. Gives a Spotify-like experience without ads and with some premium features like play in order.

  2. The application had to be limited to a small no. of users due to copyright issues.

  3. Lets admins ingest songs directly from YouTube, automating the process.

  4. Uses secure streaming to protect content.

It started as a small experiment in media streaming but evolved into a feature-rich platform.


Tech Stack Selection

Frontend:

  • Next.js → Server-side rendering + SEO for the landing & library pages + in-built router.

  • TailwindCSS + Custom Player UI → For a minimal and responsive design.

  • Zustand → Lightweight state management for the audio player.

Backend:

  • Express.js → REST APIs for auth, song fetching, and signed URLs.

  • BullMQ + Redis → Background queue for song ingestion from YouTube (planned for next updates).

  • yt-dlp → Reliable YouTube audio downloading.

  • drizzle-orm → orm for the postgreSQL on Neon.

  • Docker → To create the environment for the backend.

Database & Storage:

  • PostgreSQL (Neon) → User, song metadata, and likes storage.

  • Cloudflare R2 → Store the audio files on cloud.

There were a few problems that I faced in my tech stack selection but the learnings that followed were amazing.

The first thing I decided was to use a monorepo to manage all the different components of my application. Therefore I initialised a basic monorepo template with app/backend, app/web and packages/db. Later when I moved towards production, I realised that I could not deploy the db folder independently and use the initialised db from there into my backend. Therefore I had to move the database initialization into the backend (Although I later moved all of the db related files into the backend itself).

Initially, I had decided to go with tRPC for the backend. I had never used it earlier, so I thought that this being a minimal app, it was a good opportunity to learn the framework. Everything was working perfectly until I tried to login. I realised that the backend was not sharing the jwt token to the frontend, and thus my Next.js middleware auth protection was not working. Also, later when I started working in the mobile app, I had a lot of trouble connecting the tRPC backend there.


Flow and Working of the Application

1. User Login

  • An admin was created using a js script during the initialisation of the project.

  • Only admins are allowed to create new users. Thus, it is limited to a few users only.

2. Admin Upload or YouTube Ingestion

  • Admin can upload MP3s or paste a YouTube link (automated queue processing).

  • Metadata is stored in the DB; songs are converted and saved in S3 storage in Cloudflare R2.

3. User Streaming

  • Home page displays all songs in the library.

  • Clicking play → Fetch short-lived signed URL → Stream securely.

3. Player Features (v1 complete)

  • Play/Pause with spacebar

  • Song likes & playlist creation

  • Shuffle mode for randomized listening

  • Persistent playback state via Zustand

4. Upcoming Features (v2 goals)

  • Autoplay next song

  • AI-based song recommendations (e.g., mood or history-based)

  • Background queue processing for YouTube ingestion to scale beyond manual uploads


Challenges Faced

Monorepo

When the backend was not sharing the tokens with my frontend, I decided to move the login and logout api to my Next.js itself because I wanted to use Next.js middleware. It was not a very good move but it was the only way I could see to work with Next.js middleware and because of this, I now had to add a db initialization file into my Next.js as well. Now, the frontend was not sharing the token to the backend and was giving a ‘401 Unauthorized’ response. Therefore I corrected my mistake and moved the login and logout to the backend and I created a wrapper to check for user login in my frontend. I should have used Zustand here, but I did not know about its persist functionality till then. Later when I realised that it is only my backend which needs the db package, and also due to some production issue (which I will be talking about later), I moved the all the db related files and folders into the backend itself. Thus, my monorepo structure finally became a basic client-server structure.

tRPC

Everything was working fine with tRPC except for the token sending/receiving part to and from the frontend and I was more than happy with that. But when I started with the react-native application for this website, it was by no means that I could configure the AppRouter of the tRPC. I tried to understand the problem, fix it, change the path, escape the config by over riding the AppRouter with ‘any’ but nothing worked. Finally, I had to refactor all of my backend from tRPC to express.

Docker and Backend Deployment

I was using docker for the first time. I had to find somewhere to deploy the docker application free of cost. I searched and found that it was only render which was giving this but that at the cost of a 1 minute cold start whenever the backend becomes inactive. I decided to pay this cost instead. I was also deploying a ts backend application for the first time. OMG! The time it took me to deploy this website is almost equal to the time it took me to build the backend or maybe even more. If only I had applied some of my own brain earlier in the deployment stage rather than being dependent on ChatGPT, it would have been faster. However, I was new with docker and it was good for learning I suppose. The biggest problem of them all was that the docker file was not able to find the correct path to the index file after tsc build. Every time I saw an error, I pasted it into ChatGPT and did what it asked to correct it. Later, I realised that we will obviously have to build the ts app and the deployed app is the built one in js. I did a build on my local machine and found out that the packages/db folder was creating an issue. Therefore, that is why I had to move all my db related stuff into the backend folder. Then the path specified in the docker file for the index.js file was incorrect, which took me a while to figure out the solution as the paths were coming out to be different in my local machine and at the deployment stage as well. I suppose that it was because I was using a monorepo, I do not yet know about it. But yes, this was my most painful encounter with deployment.

yt-dlp

The whole and sole reason to use docker was yt-dlp, because the yt-dlp used here is actually a python module which is used to initialise the ytdlp in js to download the audio file from YouTube. After deployment, the running of worker was not free on any platform, thus I decided to use concurrently to run the the index file and the worker, not the best option but the cheapest one. It was then that I realised how much work actually goes into the security aspect of the websites. The youtube download using the package works absolutely fine in my local machine, but in deployment, youtube detected a robot trying to access and denied all requests. I even tried to escape the problem by giving it some other metadata like browser, ip or something etc. but the ip got blocked instead. The result, the worker was not running, I removed concurrently and removed all yt-dlp related installs from the docker. Now, I run the worker from my local machine instead, after configuring the same redis instance in my local machine.

pnpm and Expo

After I created the react native application using expo, I tested its local dev and everything was working perfectly fine. I was very excited to deploy my first mobile application. The website was good, but these apps need to be mobile apps, also I got the opportunity to learn react native and expo. When I started to build it, on EAS obviously, I was getting issues after issues after issues. Then I searched about it on the web instead of chatgpt. I found out that there were many developers facing the same problem when they were using pnpm with expo. I had to add a .npmrc with some config and then it was finally built.


Scope of Improvement & Roadmap

  • Queue song system → Add queue to play songs in specified order.

  • Autoplay & Smart Recommendations → AI-driven, based on user listening history.

  • Offline caching & PWA → Reduce server hits and enable background listening.

  • React Native App Integration → Already started with Expo for mobile support.


Pros and Cons

Developer Perspective:

  • ✅ Learned background jobs, secure media streaming, and modular full-stack design.

  • ✅ Designed scalable ingestion architecture for future improvements.

  • ❌ Current worker-deployment and Docker orchestration is tricky in cheap hosting environments.

User Perspective:

  • Smooth playback with playlists, likes, and shuffle.

  • No ads, minimal interface.

  • ❌ Currently no auto play, add to queue or recommendations (coming soon).


🔗 GitHub Repo: You Listen
🌐 Live Demo: You Listen

1
Subscribe to my newsletter

Read articles from Shwetanshu Sinha directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Shwetanshu Sinha
Shwetanshu Sinha