The problem
Music Junkie keeps a log of the concerts you’ve been to, works out the stats about your concert history, and adds a social layer: follow friends, see their shows, and find the ones you were both at.
The hard part is the data. Real concerts are messy: multi-day festivals, festivals that move venues, touring festivals with one stop per city, and lineups with dozens of artists. And a log of where someone was on which night is personal, so privacy had to be airtight.
My role
I built it solo: the spec, the database schema and its privacy rules, the setlist.fm search, and the web app. It’s a monorepo with shared types and schemas, so a planned React Native app can use the same backend.
Approach
- Add a concert from setlist.fm. Search by artist, venue or festival. For festivals you pick the day you went; multi-day festivals, festivals that change venues and touring festivals like Warped Tour (one result per city) are all handled. Then confirm the lineup, drag to reorder or trim it, and add a rating, ticket price and notes. Anything that isn’t on setlist.fm can be entered by hand.
- Your log and stats. Every show, newest first, filterable by artist, year, month, state, city or venue. Stats cover totals, money spent, per-year charts with a table view, and leaderboards for artists, venues, cities and states that link back to a filtered log.
- Social. People search, following, private accounts with follow requests, other people’s profiles and stats, an activity feed, and “Also here”: the people you follow who were at the same show.
- Respecting setlist.fm’s terms. Only a server-side Edge Function calls setlist.fm, so the API key never reaches the browser. Search results are cached for a few minutes and never saved, every page credits setlist.fm, and concert pages link to the headliner’s setlist.

Key decisions
- Enforce privacy in the database, not the UI
Every read goes through one Postgres function,
can_view(viewer, owner), applied by row-level security. A bug in a page or an API call can’t leak a private account’s shows. The cost: every query has to be written and tested against those policies. - Test the privacy rules by breaking them
The database tests cover every combination of viewer and owner, and were checked against deliberately weakened rules to prove they fail when they should. It’s extra work up front for the feature that matters most.
- Call setlist.fm only from an Edge Function
The API key stays on the server and the app controls caching and attribution in one place. The trade-off is one more piece of infrastructure to run and deploy.
- Store what you confirmed, not what setlist.fm returned
A log keeps only your lineup, the venue, the date and setlist.fm links, never song lists. That follows setlist.fm’s terms and keeps the data small, but setlists are always a link away rather than in the app.
Results
The database, privacy rules, concert search, log, stats and social features were built over two days, with 395 automated checks: 136 Vitest tests for the shared helpers and the search, festival and lineup logic (run against a fake setlist.fm client), 236 pgTAP assertions for the privacy rules, follows, writes, logging, stats and social features, and 23 API smoke tests that check the same rules through Supabase’s API.
What’s next: polish (loading and error states, accessibility, phone layouts), deploying to hosted Supabase and Vercel, then a React Native app on the same backend.
Stack & links
- --typescript
- --nextjs
- --supabase
- --postgres
- --tailwind
