← All projects

Music Space

Real-time music recommendations powered by Spotify, Deezer, and Last.fm — streaming results via SSE as they're found, with full Spotify library management, a Super Mix blender, Internet Radio, and more.

React Router v7 TypeScript FastAPI Python Tailwind CSS v4 Redis Spotify API SSE
Music Space home

Overview

Music Space came out of a recurring frustration: algorithmic recommendations had stopped surprising me. Spotify and similar platforms are good at keeping you inside your comfort zone — but not at pushing you outside it. Most of the best music I've found came from other people, editorial lists, and radio. The goal was to bring those sources together with a search interface that felt fast and immediate.

The distinguishing architectural choice was streaming. Rather than waiting for all three APIs to respond before rendering, the FastAPI backend streams results via Server-Sent Events as each source returns — so the UI populates progressively. The frontend is a full-stack project: React Router v7 on the frontend, a FastAPI backend with Redis caching, Vitest + pytest test coverage, and a Makefile dev workflow.

Role

Solo — design, frontend, backend, API integration

Status

Live and in active development

Year

2024 – 2025

Key features

SSE-streamed recommendations

Search by track or artist and results stream in via Server-Sent Events as Spotify, Deezer, and Last.fm respond — no waiting for the slowest source. A Super Mix mode blends two seeds together to find music at their intersection. Feeling Lucky picks a curated track based on mood, genre, and language.

Full Spotify integration (OAuth 2.0 PKCE)

Secure login with no client secret in the browser and automatic token refresh. Once logged in: save and unsave liked songs, add tracks to existing playlists or create new ones inline, browse your full library, see your top artists and tracks across 4-week / 6-month / all-time ranges, and pick from your listening history as recommendation seeds.

Internet Radio

Explore live radio stations on an interactive map, filterable by genre. A global audio player ensures only one source plays at a time — consistent with the 30-second Spotify preview player used throughout the rest of the app.

Charts, history & sharing

Last.fm-powered global and genre charts, plus country-specific charts. Locally persisted search history. Share any track via a generated share card (OG image built server-side with Pillow). Fully responsive — bottom sheets on mobile, slide-out navigation.

Music Space recommendations

SSE-streamed recommendation results

Music Space track card

Track detail with audio preview

Music Space Internet Radio

Internet Radio — interactive map

Music Space settings

Settings

Tech stack

Frontend

  • React Router v7 (SPA mode)
  • TypeScript · Tailwind CSS v4
  • Framer Motion · Vite
  • Vitest + Testing Library

Backend & Data

  • FastAPI · Python 3.11+
  • SSE (streaming results)
  • Redis (caching)
  • Pillow (OG image gen)
  • pytest

External APIs

  • Spotify Web API (OAuth PKCE)
  • Deezer API
  • Last.fm API

Challenges

  • Streaming results from three APIs concurrently via SSE meant the backend needed to handle partial failures gracefully — if Deezer is slow or down, Spotify and Last.fm results should still stream through cleanly. Building that resilience without blocking the response took careful async design.
  • Implementing Spotify OAuth 2.0 PKCE correctly — generating the code verifier and challenge client-side, handling the redirect callback, and auto-refreshing tokens without exposing secrets — is more involved than most third-party auth flows and required reading the spec carefully.
  • Spotify, Deezer, and Last.fm return track and artist data with different schemas, different cover art sizes, and different reliability. The normalisation layer on the backend had to be defensive enough to handle missing fields across all three without the UI showing inconsistent results.

What I learned

  • Building the FastAPI backend changed how I think about frontend data-fetching. Understanding what the server is doing — caching strategy, async concurrency, SSE transport — made me a better consumer of APIs on the client side.
  • SSE is underused. For this pattern (fan-out to multiple sources, stream as results arrive), it was the right call over WebSockets or polling — simpler protocol, native browser support, and no need for a persistent bidirectional connection.
  • Writing tests for both layers (Vitest on the frontend, pytest on the backend) forced better module boundaries. It's much harder to test code that's tightly coupled to its dependencies — and the tests caught real bugs during development.

What I'd improve next

User collections — saved recommendation sessions, personal playlists built inside the app — are the most obvious missing feature. On the infrastructure side, the Redis cache currently uses a simple TTL strategy; a smarter invalidation approach based on API response freshness would improve hit rates. I'd also like to expand the "Feeling Lucky" mode with more nuanced mood and language filtering, and add a Deezer OAuth flow to match the Spotify integration.