A full-stack app: search a track/artist/genre, or upload an audio file, and get back a ranked playlist with 30-second previews.
Short answer: you can't build this on Spotify anymore. On 27 November 2024
Spotify locked Recommendations, Audio Features, Audio Analysis, and
Related Artists behind grandfathered access only — any app created after
that date gets a flat 403, and Spotify has stated this won't change. This
build uses free alternatives instead:
| Need | Spotify (blocked) | Used here |
|---|---|---|
| "Tracks similar to X" | recommendations |
Last.fm track.getsimilar / artist.getsimilar (real collaborative-filtering data from actual listeners) |
| Genre/mood tags | audio-features |
Last.fm tag data (track.gettoptags, tag.gettoptracks) |
| Analyze a raw audio file | audio-analysis |
From-scratch DSP: tempo, RMS energy, spectral centroid, zero-crossing rate |
| Cover art + 30s preview | Spotify preview URLs (also removed) | iTunes Search API — free, no key, no auth |
Last.fm's API key is free and instant (no approval wait) at https://www.last.fm/api/account/create.
Search path (backend/src/routes/recommend.js + services/ranking.js):
- Resolve the query to a real track via Last.fm search.
- Pull three independent signals: direct track-similarity (
track.getsimilar), top tracks from similar artists (artist.getsimilar→artist.gettoptracks), and top tracks sharing the seed's genre tags (tag.gettoptracks). - Score every candidate on a weighted blend of: source reliability, Last.fm's own match score, tag overlap with the seed, and log-scaled popularity (so the playlist isn't just the 20 most famous songs on Earth).
- Deduplicate, cap tracks per artist at 2 for variety, return the top N.
This triangulates from three real, complementary data sources rather than one black-box score — which is the honest way to maximize accuracy without Spotify's proprietary model.
Upload path (services/audioAnalysis.js):
ffmpegdecodes the file to mono 22.05kHz PCM (first 60 seconds only — plenty for stable features, keeps it fast).- Real signal processing extracts BPM (autocorrelation-based tempo tracking), energy (RMS), brightness (spectral centroid via a small from-scratch FFT), and percussiveness (zero-crossing rate).
- A rule-based lookup maps that feature set to genre/mood tags (e.g. high
tempo + high energy →
dance,electronic). - Those tags feed the same tag-based ranking used in the search path.
Being straight about the limitation: step 3 is a hand-written heuristic, not a trained classifier — there's no free equivalent of Spotify's ML audio model. It's honest signal processing, and it's genre-reasonable, but it won't match Spotify's old accuracy on subtle mood distinctions. The features (BPM, energy, brightness) are shown in the UI so you can see exactly what it detected. If you want to close that gap later, the natural upgrade is running a pretrained audio-tagging model (e.g. Essentia's TensorFlow genre models) in a small Python microservice — that's a real infra addition, not a free-tier drop-in, so it's left out of this build by design.
backend/ Node + Express API (Last.fm/iTunes proxy, ranking, audio analysis)
frontend/ React + Vite app (the "Signal" UI)
Requires Node.js 18+ (for native fetch) and ffmpeg installed on your
system (brew install ffmpeg / apt install ffmpeg / choco on Windows).
# Backend
cd backend
cp .env.example .env # then paste in your Last.fm API key
npm install
npm run dev # http://localhost:8080
# Frontend (separate terminal)
cd frontend
cp .env.example .env
npm install
npm run dev # http://localhost:5173Open http://localhost:5173.
- Backend: any Node host with ffmpeg available — Render, Railway, or a
small VPS all work well. Set
LASTFM_API_KEYandCORS_ORIGIN(your deployed frontend URL) as environment variables. On Render/Railway, ffmpeg needs to be present in the build image — Railway's Nixpacks and Render's Docker builds both support installing it via anaptbuildpack/Dockerfile step (apt-get install -y ffmpeg). - Frontend: Vercel, Netlify, or Cloudflare Pages — plain static Vite
build (
npm run build→ deployfrontend/dist). SetVITE_API_BASEto your deployed backend URL.
Off-white/grey palette, zero border-radius, everything styled like analog
audio equipment (VU-meter waveform, transport-style play button, mono
data readouts for BPM/match %). No component libraries — hand-rolled CSS
with variables in frontend/src/styles/theme.css if you want to retheme it.
-
npm auditon the frontend will flag a moderate esbuild advisory pulled in by Vite 5 — it only affects the local dev server (a malicious site could read dev-server responses whilenpm run devis running), not the production build. Fix it withnpm audit fix --force(bumps to Vite 8) if you want; it wasn't force-upgraded here since that's an untested major version jump. -
Last.fm and iTunes are both free tiers with real (generous but finite) rate limits — the backend caches lookups for 30 minutes by default (
CACHE_TTL_SECONDS) to stay well within them. -
Not every track has an iTunes preview match — those show a disabled play button rather than a broken one.
-
Audio analysis quality depends on the uploaded file's mix/mastering; very quiet or highly compressed masters can skew the energy reading.