

For the curious, the lineage goes: Soprano v1, v2, v3, soprano-ng, muse, music, and now Soprano again. I may have missed one.
The honest answer for why is: Echo. My last music app -- called, creatively, "music" -- was built on a very early version of Echo, my own PHP framework. Echo's backend moved fast in those early days, and I kept changing my mind about the foundational stuff: how composition should work, how migrations should behave, what the console should look like, how the admin backend should be wired. Every time upstream Echo shifted, my fork drifted further. Eventually merging changes from upstream became more painful than starting over.
So I had a better idea. Rewrite it. On the new Echo. The way god intended.
Traefik changed the game
The other thing that nudged me into this rewrite was finally figuring out Traefik for local dev. I'd been avoiding it for a while because the docs make it look more intimidating than it is. Once I got it working with Docker, everything clicked. I can now run Echo, williamhleucka.com, and Soprano at the same time -- all in Docker, all reachable by hostname, no port juggling, no localhost:8080 vs localhost:8081 nonsense. It's the first local dev setup I've had in years that doesn't make me want to flip my desk.
Echo's ORM is finally growing up
Soprano is also a chance to exercise some of the recent Echo changes. The ORM in particular got a real upgrade in v0.8.0:
- paginate() -- actual pagination instead of me writing the offset math by hand for the hundredth time
- orderByRaw() -- escape hatch for when you need to sort by something the query builder doesn't natively support
- having() -- finally, aggregate filtering without dropping to raw SQL
There's still no JOIN (and yes, the chained where foot-gun still exists -- if you know, you know), but Echo's ORM is increasingly something I can actually live with for a project this size. The next batch of Tier 2 sugar -- orderByDesc, latest/oldest, pluck, exists, standalone limit/offset -- is queued up for v0.8.1.
Where Soprano is going
Right now Soprano plays the music files on my home server. That's the MVP. From here, the roadmap is:
- HD radio streams -- I want one app for both my library and the stations I actually listen to
- Podcasts -- via the ListenNotes API
- Maybe movies and TV someday -- a Jellyfin-shaped feature, no promises, but I keep thinking about it
The unifying idea is: one app, on my hardware, for all the media I consume. No subscriptions. No "this content is no longer available in your region." No algorithm deciding what I get to hear next.
Why I keep doing this
I still hate Spotify. I hate YouTube's ad load. I hate that every piece of media in 2026 wants a monthly fee and a login. This is why I keep writing these dumb projects.
Until next time. Happy coding 🌈
Leave a Comment