Inspiration
Spotify's recommendation engine is very good at one thing: giving you more of what's already popular. It's much worse at the other thing, giving you more of what's genuinely alike. If your taste sits outside the mainstream, that gap turns into a wall. This project started as a "Tinder for movies" concept, but that format was already saturated. Music wasn't, and it connected to a real, personal frustration instead of a trend. The core bet: niche discovery shouldn't depend on how popular something already is.
What it does
Showmi is a swipe-based music discovery app. You swipe through 30-second clips: pass, like, or rate what you've already heard from 1 to 5 stars. The rating is what actually moves the recommendation engine, not the swipe itself. The differentiator is the anchor mechanic: lock the deck to one song or artist and ask for "more like this." That request builds a real similarity chain through Last.fm's community tagging data, not a popularity ranking wearing a disguise. A vibe selector lets you filter the session's mood without losing your genre thread. Everything you save lands in a personal library, and a companion pet grows in step with how deep you listen to each genre.
How we built it
Client: Expo (React Native) with TypeScript, Zustand for local state, TanStack Query for server state and caching.
Backend: Supabase, running 17+ Postgres tables with Row Level Security, six Edge Functions, and six scheduled cron jobs.
Recommendation engine: built and unit-tested as its own standalone package, then vendored into the app. Thompson Sampling (Beta-Bernoulli) across five dimensions (artist, decade, popularity, genre, vibe), with a hard filter that assembles the candidate pool before ranking, and relaxes automatically if that pool gets too thin.
Data, split by role instead of by convenience:
- iTunes Search API for the actual audio (30-second clips, no key required)
- Last.fm for similarity and genre tags, called through an Edge Function so the API key never reaches the client
- Spotify Web API via Client Credentials for the exact track link, with no user login and none of the developer-mode tester limits that a login flow would trigger
Cross-source matching runs on normalized artist + title, since iTunes never exposes an ISRC to match against.
Monetization: RevenueCat, with a showmi_more entitlement gating unlimited swipes, no ads, and exclusive cosmetics, behind a paywall built from scratch to match the app's own visual identity rather than a default template.
Challenges we ran into
- Building solo, as a law student. No engineering background going in, learning the stack while carrying a full course load in Mexican civil, criminal, constitutional, and labor law.
- A platform shift mid-project. Spotify deprecated both
preview_urland its/recommendationsendpoint in November 2024, I noticed right as this project needed them. The fix was rebuilding audio and discovery around iTunes and Last.fm instead of Spotify. - A silent cold-start bug. Genre and vibe answered during onboarding were being ignored by the deck; the engine needed to be seeded before the first swipes, not after. Traced and fixed.
- A one-word bug that would have broken monetization invisibly. The code checked for an entitlement called
premium; the real one configured in RevenueCat wasshowmi_more. Undetected, no purchase would ever have registered as valid, and there'd have been no error to point to why. - A missing build configuration, caught the day before writing this.
eas.jsondidn't declare environment variables in any profile. Since.envis gitignored, the next build would have shipped with empty RevenueCat and Supabase keys: the paywall would show no plans, and the deck would have nothing to load. Caught and fixed before it reached a device. - No personal Android device. Testing the purchase flow end to end depends on borrowing one.
Accomplishments that we're proud of
- A recommendation engine that holds up under test: 37 of 37 passing.
- A real performance pass, measured before and after, not assumed: session request count per deck load down from 103 to 9, and total latency from 66 seconds to 7.2.
- A real security fix, not a hypothetical one: found and closed 12 unprotected database tables and one IDOR vulnerability the same session.
- A visual identity that says no more often than it says yes: one brand color, one atmospheric gradient reserved for exactly three screens, and a standing rule against decorative shadows and generic gradients everywhere else.
What we learned
That an unverified assumption, an entitlement name, an environment variable, can sit quietly wrong for weeks and only surface right before a build or a demo. That building alone means the second pair of eyes a team would normally provide has to be replaced by deliberately re-checking against the real, running project instead of trusting what worked yesterday. And that chasing "more like this" turned out to be a better problem than chasing "more accurate audio analysis": Last.fm's community tagging gets you further, faster, than trying to fingerprint sound itself.
What's next for Showmi
- Move the user's library and taste profile from local, on-device storage to full persistence on Supabase, so switching phones doesn't erase it.
- Real store credentials (Spotify Client ID/Secret, Apple Developer Program, production RevenueCat keys) to move from a Test Store to an actual public release.
- Expand genre-to-vibe tagging coverage beyond its current ~20%.
- Surface a visible signal when the deck's filter relaxes past an exact genre+vibe match, so the person swiping knows why they're seeing something slightly off their request.
Built With
- claude
- deno
- expo.io
- plpgsql
- shell
- typescript
Log in or sign up for Devpost to join the conversation.