Case Study · 2024

Polish

A real-time music matching web application that connects strangers who are listening to the same song at the same moment.

Full Stack Real-Time FastAPI JavaScript SQLite
Live Demo GitHub
music-polish.onrender.com — Polish · Music Matching
Polish app main dashboard — library and Now Playing panel

Role

Full Stack Developer

Timeline

6 Weeks

Stack

Python · FastAPI · JS · SQLite · WebSocket · JWT

The Problem

Music is one of the most powerful ways humans connect — but today we listen alone. Millions of people play the same song at the same moment, in the same city, without ever knowing each other exists. Existing music apps like Spotify have social features, but none tell you "this stranger 2km away is listening to exactly what you're listening to, right now."

The question was: Can we build a system that detects this real-time musical overlap and creates a moment of connection?

SIMULTANEOUS LISTENERS

0

APPS THAT CONNECT THEM

Now

POLISH CHANGES THAT

The Solution

Polish is a full-stack web application that:

music-polish.onrender.com — Polish · Sign In
Polish app login screen

Try It Yourself

The app is live on Render. To experience the music-matching feature, open it in two different browser tabs (or share the link with a friend) and log in with different accounts — then both play the same song to see the match happen in real time.

01

Open the app

music-polish.onrender.com ↗

May take ~30s to wake up on Render's free tier.

02

Create accounts

Click Create account — just pick any username + password. No email needed. Do this in Tab A and Tab B with different usernames.

03

Play the same song

In both tabs, click the same song from Your Library. The app detects the overlap and shows a match notification.

music-polish.onrender.com — Now Playing
Polish app now playing state — song selected with pause button

Tech Stack

Backend

Python FastAPI SQLAlchemy ORM SQLite bcrypt JWT (PyJWT)

Frontend

JavaScript (Vanilla) HTML5 CSS3

Infrastructure & APIs

WebSocket Geolocation API Nominatim API Polling (3s interval)

Key Features

01

Real-Time Music Matching

Every 3 seconds, the app logs the user's current song + GPS coordinates to the backend. The server checks for other active users playing the same song in the last 30 seconds. If found, a "Listening Nearby" panel appears instantly — no page refresh needed.

02

Secure Authentication

Users sign up and log in with bcrypt-hashed passwords. The server issues a JWT token on login. Every protected API call sends this token in the request header — no sessions, no cookies.

03

Live Chat with Typing Indicators

When two users match, they can open a direct chat panel. Chat history is stored in SQLite and loaded via REST. Typing indicators (animated bouncing dots) and live text preview are powered by WebSocket — the other person sees your text appearing character by character before you hit send.

04

Geolocation Integration

The browser's native Geolocation API sends real coordinates. These are reverse-geocoded using the Nominatim API to show human-readable city names. If GPS is unavailable, the app defaults to 0,0 and matching still works by song name alone.

05

Responsive Dark UI — Built From Scratch

Built with no UI libraries. Dark ambient theme with vinyl disc animation, per-song gradient colours, glassmorphism cards, and smooth CSS animations throughout. Every component is hand-coded, every pixel intentional.

Challenges & How I Solved Them

Challenge

Ghost Users

Old users remained "active" in the database even after closing the browser, polluting match results with stale data that never expired.

Solution

Timestamp-Based Expiry

Added a last_active timestamp to every log request. The match query now filters to only users active in the last 30 seconds. Any user who closes the app disappears automatically within 30 seconds.

Challenge

Blank Page Bug

After a UI redesign, the entire app section was invisible after login. Hours of CSS debugging found absolutely nothing wrong.

Solution

One Wrong HTML Tag

A single typo — </auth-section> instead of </div> — caused the browser to nest the entire app inside the auth section. Fixed with one character change.

Challenge

Chat Ordering Race Condition

New messages occasionally appeared before older ones because the DOM was being fully cleared and rebuilt on every poll cycle.

Solution

Incremental DOM Updates

Tracked the previous message count and only applied the slide-in animation to genuinely new messages. Preserved scroll position if the user had scrolled up to read history.

Challenge

Real-Time vs REST Trade-off

The initial architecture used pure HTTP polling for everything — slow and inefficient for typing indicators which needed sub-second latency.

Solution

Hybrid Architecture

Kept REST for persistent messages (stored in DB) and added a WebSocket layer exclusively for ephemeral events like typing and live text preview. Best of both worlds — durability where it matters, speed where it counts.

Outcomes

01

Built a complete full-stack application end-to-end independently — from database schema to responsive UI.

02

Implemented JWT authentication, bcrypt hashing, REST API, WebSocket, and geolocation — all from scratch, no starter templates.

03

Wrote 10 unit tests covering all critical backend endpoints with pytest.

04

Designed a fully responsive UI with custom animations and zero UI framework dependency.

05

Resolved 4 non-trivial technical bugs through systematic debugging — HTML nesting, race conditions, ghost users, polling inefficiency.

06

Gained hands-on experience with real-time systems architecture and meaningful trade-off decisions.

What I Learned

This project taught me that the most dangerous bugs are not in the logic — they are in the assumptions. I spent hours debugging CSS when the real problem was one wrong HTML closing tag.

I also learned the difference between building a feature that works in theory and building one that works in reality — the ghost users bug only appeared because real humans don't close apps cleanly the way test scripts do.

Most importantly: every technical decision is a trade-off. REST vs WebSocket. Polling vs push. SQLite vs Postgres. The right answer depends on the scale and constraints of the problem, not on what sounds most impressive.

Get in touch

Vinay-Kumar-Resume.pdf