Bing Beats.
Discover music across time and space. Pick any country and any year from 1950 to today, and Bing Beats surfaces the tracks that defined that place and moment — with 30-second previews and an AI guide that knows the local scene. "What did the world sound like in Brazil in 1998?", turned into a tappable, playable playlist.
Bing Beats is a cross-platform music-discovery app built with Expo / React Native. The whole interface breathes — accent colours, buttons, and the globe's glow cycle through the full spectrum on a ~12-second loop, so the app feels alive without any input — while the core loop stays simple and tactile.
The loop
- Choose a place — tap a country from a flag grid, or spin an interactive 3D globe and tap it directly.
- Choose a time — drag a year slider spanning 1950 → present.
- Discover — get a curated playlist for that country and year, each track with cover art, a 30-second preview, and a link to open the full song in Spotify.
- Ask the guide — a context-aware AI chatbot explains the genres, scenes, and cultural context of that place and era, and can generate a playlist on request.
Two halves, keys on the server
The project is split so that private API keys never touch the client. The Expo app owns the experience; a lightweight Express service acts as a secure proxy and orchestrator.
Expo · React Native
Expo Router for file-based navigation, heavily animated with Reanimated: a
wordmark that assembles letter-by-letter, a popping year counter, haptics, shimmer skeleton
loaders, and a single self-cleaning expo-av audio player. Shared React Context
holds the selected country/year and drives the app-wide cycling gradient.
Node · Express (Render)
A secure proxy that authenticates, ranks, and enriches — so the app bundle ships with no secrets. It fuses Spotify, iTunes, Gemini, and Mapbox behind four small endpoints.
POST /api/playlist— authenticates with Spotify (Client Credentials, token cached), searches that country's market for the year, dedupes and ranks, returns the top tracks.POST /api/chat— builds a country/year-scoped prompt, enriches it with the real track list, and calls Google Gemini.GET /api/public-config— hands the client a public Mapbox token for the globe.GET /health— reports which integrations are configured.
Tech stack
- Framework — Expo, React Native, Expo Router, TypeScript.
- UI / animation — React Native Reanimated, Expo Linear Gradient, Expo Haptics, and a custom cycling-gradient theming layer.
- Audio —
expo-avfor 30-second preview playback. - Maps — Mapbox GL, rendering the interactive globe inside a WebView.
- Backend — Node.js, Express, CORS.
- Music data — Spotify Web API, backfilled by the iTunes Search API for previews.
- AI — Google Gemini (
gemini-2.5-flash-lite). - Hosting — Render for the backend; Expo Go as the app runtime.
Engineering challenges
- Spotify hides its preview URLs. The Web API often returns a null
preview_url, which would leave the play button useless. For every selected track the backend does a secondary iTunes lookup and backfills a real 30-second clip — sharply increasing the number of actually-playable tracks. - An undocumented rate limit. Spotify's docs claim search limits up to 50, but the
live API rejects anything above 10 with
400 Invalid limit. Only real testing surfaced it; search now pages withlimit=10and gathers candidates across requests. - Render's cold starts. The free tier spins down when idle, adding a 30–60s delay on the first request. That drove honest loading states — shimmer skeletons and progress messaging — so the wait feels intentional rather than frozen.
- A 3D globe inside React Native. With no native globe component, Mapbox runs in a WebView. Feeding it the cycling accent colour naively would flood the bridge, so the hue is quantized into buckets — the globe updates ~24 times per colour loop instead of hundreds of times a second.
- Gemini quota & fallback. Free-tier models hit
429under load, so the server defaults to a higher-headroom model and exposes an env override — the model can be swapped without a code change when a quota is exhausted. - Keeping the chatbot useful. Playlist requests are detected by intent and short-circuited to return a real, tappable playlist rather than a generic text list — bridging the gap between "chatbot" and "functional feature."
What's next
- A real database to persist saved playlists and preferences (currently in-memory).
- Conversation memory and stronger topic-guarding, so the AI guide stays focused across a longer chat.
- Richer loading states to further mask cold-start latency.