This is a submission for the GitHub Finish-Up-A-Thon Challenge
What I Built
VKara is a browser-based karaoke room app for singing at home with friends or family.
It is not trying to replace YouTube.
YouTube is already great at playing videos. It already has almost every karaoke song we need. But YouTube is not really designed to manage a karaoke night where many people want to choose songs together.
That is the gap VKara tries to fill.
You open VKara on a TV or laptop as the main playback screen. Everyone else joins the same room from their phone using a 4-digit room code or QR code. Then anyone can search for songs, add them to the queue, pause, resume, or skip.
The TV only needs to play the video.
Everyone's phone becomes their own remote.
That is the whole idea.
Simple enough to explain in one sentence. Not simple enough to build in one weekend. I learned that part the hard way.
Demo
Links:
- Live demo: https://vkara.vercel.app/en
- GitHub repo: https://github.com/lehuygiang28/vkara
- Before branch: https://github.com/lehuygiang28/vkara/tree/before
- Old backend repo: https://github.com/lehuygiang28/vkara-api
Small warning: the demo is running on limited resources, so if it is slow, please give it a moment. My wallet is still a student wallet. lol.
The flow is:
- Open VKara on a TV or laptop.
- Join the room from a phone by code or QR.
- Search for a karaoke video.
- Add it to the shared queue.
- Control playback together.
Before: the idea worked, but the product still felt like a video app squeezed into a karaoke use case.
After: the mobile flow is now focused on joining, searching, choosing an action, and controlling playback.
What Changed
Before:
- Frontend and backend lived in separate repos
- Mobile UI was basically a compressed desktop UI
- WebSocket contracts were spread across projects
- The TV screen had too many controls
After:
- Merged everything into a Bun workspace monorepo
- Moved shared API/WebSocket contracts into packages/shared-types
- Rebuilt the product around two roles: TV as screen, phone as remote
- Simplified the mobile flow around join, search, queue, and controls
- Improved room state, reconnect behavior, playback sync, and deployment
The Comeback Story
I started VKara around early 2025.
At that time, my goal was very personal. I wanted a better way to sing karaoke at home with friends. The normal setup was: open YouTube on a TV, search for karaoke videos, and pass control around.
It worked, but it was awkward.
One person was searching. Another person accidentally played a video immediately. Someone else wanted to know what song was next. The queue was unclear. Casting also usually depended on everyone being on the same Wi-Fi network.
So I built a prototype.
The first version technically worked. And by "worked", I mean the dangerous kind of worked: it worked enough to prove the idea, but not enough to confidently bring it to a real karaoke night.
The product had problems:
- the desktop screen had too many controls around the video
- the mobile UI felt like a compressed desktop app
- search, queue, and controls were too close together
- realtime state was fragile
- the frontend and backend lived in separate repos
- shared types and WebSocket contracts were not cleanly owned by one place
That last part mattered more than I expected.
VKara is a realtime app. The frontend and backend have to agree on room state, queue state, video metadata, player commands, and WebSocket messages. When those contracts live in different places, every change feels risky.
So I stopped working on it.
Not because the idea was bad.
Because the project was hard to change.
When I came back this year, I decided not to start with new features. I started by making the project safe to change.
The first major step was merging the old frontend repo and old backend repo into one Bun workspace monorepo:
apps/web # Next.js frontend
apps/api # Elysia + WebSocket backend
packages/shared-types # shared API/WebSocket contracts
packages/shared-utils # shared helper functions
This was not just folder cleanup. It changed how I could work on the project.
After the monorepo migration, web and API changes could be checked together. Shared contracts could live in packages/shared-types. I could change a room event or video shape without hunting through two repos and hoping I remembered everything.
The second major change was UX.
At first, I treated VKara like a responsive website:
Desktop layout. Mobile layout. Done.
That was the wrong mental model.
A TV is not just a large phone. A phone is not just a small TV.
They have different jobs.
So VKara now thinks in two roles:
- TV / laptop: the playback screen
- Phone: the remote
That one decision made the product much clearer.
The TV should stay clean. It should show the video, the room code or QR, and the basic room controls.
The phone should handle joining, searching, adding to queue, and playback actions.
The real flow became:
Join a room → search a song → add it to the queue → keep singing.
A lot of the comeback work was not glamorous. It was the kind of work users only notice when it fails: WebSocket room state, reconnect behavior, playback sync, queue updates, room cleanup, YouTube metadata shape, Docker builds, GitHub Actions, and Vercel deployment after the monorepo migration.
Nobody at a karaoke night cares how clever the architecture is.
They only care that when they tap a button, the room responds.
My Experience with GitHub Copilot
The biggest difference between the first version and this comeback was not only GitHub Copilot.
It was how I used it.
In the first version, my AI-assisted workflow was too naive. I asked for code, patched bugs by hand, and moved fast. That helped me build a prototype, but it did not always leave behind code I trusted.
This time, I used GitHub Copilot more like a finishing partner.
Before changing code, I used Ask Mode to understand the project again. VKara had been sitting for a long time, and honestly, I did not remember every decision I made.
One of my first questions was basically:
"Why am I not using SWR or React Query in this frontend? What am I actually using instead?"
The answer reminded me that the app was using Zustand stores for client state, direct fetch calls for server data, and WebSocket-driven realtime sync for room/player state.
That question saved me from changing the wrong layer.
Ask Mode helped me understand the old data flow before editing code.
For larger work, I used Plan Mode before execution.
Instead of saying:
"Make these two projects cleaner."
I asked for a real migration plan:
"Merge
vkaraandvkara-apiinto one monorepo, keep history, use Bun workspace, find shared types, and move contracts into shared packages."
Plan Mode turned a vague refactor into a safer migration path.
That became my loop:
Think → Ask → Plan → Execute → Verify → Repeat
For bugs, I pasted exact TypeScript errors or Docker logs.
For UI work, I used screenshots and described what felt wrong.
For architecture questions, I asked for alternatives before touching code.
That was the real upgrade.
In the first version, I mostly asked AI to generate code.
In the comeback, I asked GitHub Copilot to help me investigate, compare, plan, refactor, verify, and explain trade-offs.
The final result is still not perfect.
But now I know what actually matters.
Not “AI lyrics” or some huge feature that sounds cool in a roadmap.
The real problems are smaller and more human: who is allowed to skip, who added this song, whose turn is next, how to stop one person from filling the whole queue, how to rejoin quickly when someone closes their phone, and how to bring back the same queue for the next karaoke night.
YouTube already gives us the videos.
VKara should make the room feel calm.
But VKara is no longer just a prototype I keep for myself.
It is something I can open on a TV, hand my phone to a friend, and say:
"Pick the next song."
And honestly, that already feels like a win.




Top comments (2)
This is a strong finish-up story because the “comeback” was not just adding features. It
was making the project safe to change.
The TV/phone role split is the key product insight here.
At first glance, karaoke sounds like a video problem. But the real problem is room
coordination: who can add songs, what is next, who controls playback, what happens when
someone reconnects, and how the queue stays understandable while people are actually
singing.
That is why the monorepo/shared-contract work matters. For a realtime room app, frontend/
backend agreement is the product. If the WebSocket event shape drifts, the UX breaks
immediately.
I also like that you avoided the obvious “add AI lyrics” kind of roadmap thinking. The
most valuable next features are probably still human-room features: turn order, queue
limits, host permissions, rejoin recovery, and maybe saved room history.
“TV as screen, phone as remote” is simple enough that users can understand it instantly.
That is usually the sign that the product finally found its shape.
This is great!