Web Development/4 min read
Building JamSesh, a place for musicians to find each other
How I built the Go and PostgreSQL backend for JamSesh, a platform where musicians find each other and record duets, with WebSocket chat and a scoped-down MVP.
JamSesh started as a semester project with a startup feel: come up with a product, find out who it's for, and build it. It became a platform where musicians find each other by instrument, genre and location, chat, and make music together by recording duets.
I was one of two backend developers, together with my teammate Jaron. We set up the server infrastructure, the database design and the API that the Flutter app talks to.
Choosing the idea
In week one everyone pitched startup ideas. I brought four, including a TikTok-style platform for educational videos for kids and a voting platform for teenagers. None of mine won, and that was fine: two rounds of voting picked JamSesh, because it solves a clear problem (finding musicians to play with is hard), it was feasible in the time we had, and it had room for interesting features like real-time chat.
My most useful contribution in that phase was looking at each idea from the backend side: what would it actually take to build? It kept us from picking something we couldn't finish.
From personas to user stories
To make sure we built for real people, I wrote three personas, a UX designer, an ICT student and a data analyst, each with different goals. They shaped the features directly:
- filters on genre, instrument and level, so people find relevant matches fast,
- chat, so first contact is low-effort,
- flexible collaboration without fixed commitments.
I turned those needs into prioritised user stories: create an account, create a musician profile, search by instrument and location, start a jam, share your location safely. Once those were written down, the team argued much less about what to build.
The architecture
Together with Jaron I drew the context diagram, which made it obvious what we should not build ourselves: notifications and maps would come from external services. The main choices:
- Go for the backend, for its strong concurrency support. Chat means many open connections at once, and goroutines make that cheap.
- PostgreSQL, because the data is highly relational: users, instruments, genres, rooms and sessions all point at each other, and we wanted the database to guarantee integrity.
- WebSockets for chat, because polling over HTTP feels sluggish for a conversation.
- Cloud storage for media, not the database. Video and audio files don't belong in PostgreSQL; the database only stores a reference.
The MVP: duets instead of live jamming
The original dream was live video jam sessions. As the backend developer, I argued against it for the first version. Playing music together live over the internet is extremely sensitive to latency: even a delay that's fine for a video call makes it impossible to stay in time.
Instead I proposed a duet feature, TikTok-style: one musician uploads a track, and others record a second part on top of it. It's asynchronous, so a slow connection only means a slower upload, not a broken session. It needs no live-streaming infrastructure, and it still delivers the core value: making music together, even at a distance.
The MVP became profiles, search by instrument and location, invitations with basic notifications, rooms with chat, and duets.
What I built
- Rooms and chat: users create rooms and talk in them. Rooms are the heart of the platform.
- Real-time messaging over WebSockets, so messages arrive instantly.
- Video upload and duets, with the files in cloud storage.
I underestimated WebSockets at first. Real-time features have more states than a normal request and response, and some bugs only showed up in specific situations, like an empty room or two actions happening at the same moment.
Making it stable
The last two weeks weren't about features; they were about making the existing ones reliable:
- Edge cases: empty chat rooms, simultaneous actions, users leaving mid-conversation.
- Performance: indexes on the queries we ran most, and more efficient message handling on the WebSocket side.
- API quality: consistent error messages, better input validation, and written API documentation.
- Testing: full user scenarios (account → room → chat) and load tests with many simultaneous users.
What I learned
- Feasibility is part of a good idea. The best concept is the one you can actually ship.
- Cut scope where the risk is. Duets kept the core value without the hardest technical problem.
- Real-time is harder than it looks. Connection state is where the bugs live.
- Test early. I spent more time fixing bugs at the end because I'd tested too little at the start.
Blog / Keep reading
Related articles.
Decentralized Web/5 min read
SilentEcho, a diary the server can't read
An end-to-end encrypted journal with Flutter, Go and a web portal.
Web Development/3 min read
A podcast studio CMS with a remote-controlled teleprompter
Vue, Symfony, Electron and one small WebSocket server for a podcast studio.