Inspiration
College clubs run on GroupMe, and GroupMe was built for small talk. We watched a university running club hold itself together by hand. The weekly workout plan was a spreadsheet screenshot pinned in the chat. Every race got a brand new group chat that died a week later. Announcements were ordinary messages that someone hopefully remembered to pin. The officers kept a second, secret group chat that never quite matched who the officers actually were.
None of this was anyone's fault. The tool just had no place for the things a club actually does, so members kept faking those things with screenshots and extra chats.
What it does
ClubChat is the group chat your club already knows, plus the structure it has been faking.
- Chat is the center. Everything else posts back into it, so nobody has to learn a new habit.
- Announcements and highlights look like announcements and don't get buried under chatter.
- A weekly schedule that's dated and searchable instead of a picture. Clubs call it whatever they want: workouts, rehearsals, shifts.
- Races, shows and trips each get their own space with a roster, carpool groups and a chat, and the space is still there afterward.
- An officers' space that updates itself when someone's role changes.
- Calendar, events, polls, photo galleries, direct messages and push notifications, plus blocking, reporting and moderation built in.
Nothing in it is built around one sport. A theatre troupe, a chess club or a volunteering group can use it with nothing to set up.
How we built it
- App: React Native with Expo, so one TypeScript codebase runs on iPhone, Android and the web. Messages are saved on the phone, so chat opens instantly and keeps working through bad signal.
- Server: Node.js, split into three pieces: an API, a live-messaging server for instant chat, and a background worker for notifications and other follow-up work. Postgres and Redis store the data.
- Photos: stored in Cloudflare R2 and served through a Cloudflare Worker, which checks that you're allowed to see a photo before it sends it.
- Hosting: Fly.io for the servers and Neon for the database. Sentry reports errors. App fixes ship over the air, so phones get them without waiting for an App Store review.
- Safety by design: every "who is allowed to do what" rule lives in one place and is written exactly once. Data rules are enforced by the database itself, and we prove each one by deliberately trying to break it.
Challenges we ran into
- A bug that kept saying "OK". The iPhone was quietly encoding its catch-up request twice, and the server answered "200 OK, nothing new" every time. Live chat hid the problem, so phones looked up to date. Any message missed while a phone was offline was just gone. The only clue was that the phone's request didn't match the browser's.
- The browser lied to us. Some things worked perfectly in a web browser and failed on an iPhone. For example, iOS silently refuses to open a popup from inside another popup. Now we test on a real device before we call anything done.
- Messages handled out of order. The server sometimes processed a phone's second message before it had finished with the first, so members with perfectly good logins got signed out at random.
- Error reports that never arrived. A wrong key meant every crash report was rejected, while five separate layers reported success. We only found out by sending a test error by hand and reading the raw response.
Accomplishments that we're proud of
- It's live. Real servers in production, an iPhone app on TestFlight, and fixes delivered to phones over the air.
- 2,191 automated tests, and every one passes.
- Every bug a real person hit is written down, including what went wrong while we were fixing it. That last part is the one people usually skip.
- It costs about $37 a month to run, and it was load-tested at ten times the traffic we expect at peak.
What we learned
- The worst bugs are silent. A crash gets fixed right away. A check that never runs, or a request that returns "OK" with nothing in it, can hide for a long time. Now we look hardest at code that reports success.
- Reproduce the bug before fixing it. Several of our first diagnoses were confident and wrong. Trying it in the real app, on the real device, settled things faster than reasoning about the code.
- A test you've never seen fail proves nothing. We write the test first and watch it fail before we write the fix.
- Simple beats configurable. ClubChat works for any club because it asks nothing about what kind of club you are, not because it has a setting for every kind.
What's next for ClubChat
- Meet Clubby, the ClubChat assistant. We're adding MCP support so every club gets Clubby, a voice assistant built into the app. Just say what you want: "post that practice moved to 6", "what's on this week", "who's driving to Saturday's race". Clubby writes the message, finds the event, or takes you to the right screen. Anyone in the club can use it, so getting around ClubChat is as easy as talking.
- Our first real club. The goal is one club whose members use ClubChat every day instead of GroupMe. A club that loses a single message goes back to GroupMe, so reliability comes first.
- Then every club on campus. Once one club trusts it, the same app works for all of them with no changes.
Built With
- auth
- docker
- drizzle
- eas
- expo.io
- fly.io
- neon
- node.js
- react
- reddis
- resend
- sentry
- sharp
- sql
- testcontainer
- testflight
- typescript
- vitest
- websockets
Log in or sign up for Devpost to join the conversation.