The Problem
Groups of friends get together to play golf events over the course of a weekend. Playing against each other on different teams in order to gain points for their team. Players would prefer if during the round, they could see where other players stand amongst scoring. Generally, players must message or wait until after the round to find out results. Solutions were pondered, either lacking functionality or too expensive; I decided to just build it.
Requirements
- Each event team has an even amount of players
- Each match is 1v1 or 2v2 where respective teams have one score to submit per hole
- Scores must be confirmed by the other party each hole or face reconciliation
- Upon confirmation, update game state, and move match to the next hole.
- Represent event and user scoring through leaderboard.
- Event admin dashboard (players, dates, rounds, courses, content)
Initial Thoughts
First and foremost I wanted to build something that worked, but almost equally as important I wanted a seamless user experience. After all, golf is a very cerebral game, and the last thing anyone needs is tech trouble when they're already upset they just lost the last hole because of an errant putt. I knew using Ionic would afford the ability to create a refined mobile experience, while also supporting desktop. It meant I could create a good looking PWA users can add to their home screen; porting to a native mobile app if it grows further. Tough questions that didn't have initial answers were based around how the app would handle active rounds, how players could communicate with other players via the app to confirm scores quickly, and how game state would stay safe.
Answering Tough Questions
Players that have been related to an Event will see their upcoming event, as well as rounds they're apart of. Additionally their specific group, tee time, and starting hole are engageable. On a separate play tab, users will see an active game interface on the day of their event, after their tee time. Locale is additionally stored on an Event to handle whether it is available. Most functions of the play tab and the leaderboard tab utilize websockets via SocketIO to create an event driven UI that can respond to various game state updates in order to reduce clicks and friction.
Each EventRoundMatch contains a unique UUID which assists in tracking match state on the backend. Initially, each team submits a score for the hole, this sets a locked Redis key related to UUID + hole composite atomically. If a key related to that composite already exists we know the other team has already submitted a score for that hole. Socket events then trigger a UI decision to confirm your opponents score. Decisions on whether to confirm or contest an opponents score then trigger events to update UI. Once the backend has dual confirmation, it updates the overarching game state and sends off another event to update/move the UI forward.
To preserve gamestate in the event of failures, most related writes and some logic live inside of transactions. This ensures that when failures happen, the game isn't left in a broken UI state. I also utilized Prisma postgres for both connection pooling and competing writes.
Conclusion
In the end, I met all initial criteria. I tested the app in various small exhibitions over Spring 2025, it was then used for a 3 day event in Chicago with 26 players that worked flawlessly. 1404 hole submissions later and I was left with something to be very proud of. The current plan is to use it as is for The Des Moines Ryder Cup in August. I would then like to expand some of the core game functionalities to be more flexible with configuration options. This is with the goal of creating a product that could be used for competitions of all kinds. Whether it be a friendly outing, charity event, golf league, or serious competition; I believe the core of the app has me at over half way to something worth offering.