Check out the online board game:
Table of Contents:
- Game Breakdown
- Incorporating Players Without Authentication
- Real Time Updates
- Under the hood
- Conclusion
Being a software developer is a very rewarding profession. I love to build cool things, and the software that I build in my free time is almost exclusively a solution to a problem that I am facing. My family loves playing Boggle, a popular word game, when we are all together. Now that my brothers and I have all moved out of our parent's house, we seldom get to enjoy family game nights like we once did. In an effort to solve this issue, I decided to recreate the beloved Boggle but open-source and free!
Game Breakdown
Just like starting out any other project, of course I took to the whiteboard (Excalidraw). Laying out the blueprints for the game is an important first step when building.
When outlining the project, I came up with a couple of different pages to tie the whole game together.
- Initial landing page
- /lobbies page
- /lobby_code
- /[lobby_code]/[player_name]
The way that the game works is quite simple: the player will initially land on the home page where they will be asked to create a game or join a lobby. Players can click a button to navigate to the /lobbies page where they can view any publicly open lobbies (Lobbies are set as public or private upon creation).
I needed to track a couple of persistent variables for each lobby, such as: - Game state (active/inactive): Boolean
- Board configuration (letter arrangement): String[]
- Players within a lobby: String[]
- Lobby status (public or private): Boolean
The game state is changed when the lobby's game starts or ends as you would imagine. The board configuration manages how the letters are composed on the board so that the letters/order are persistent across clients (generated on each game start). When a player joins a lobby they will be prompted with an input for a name. Their input is sent to the boggle_game table row that matches the lobby code that the player is attempting to join. Players creating a new lobby are prompted with the same + an option to make the lobby public or private, and a random lobby code is generated on creation of the new lobby.
Incorporating Players Without Authentication
Typically on an application that requires multiplayer connectivity, developers would opt for an authentication solution to save player data to an account. After careful consideration, I decided that I wanted the game to be as simple as possible without needing account creation. This decision has it's pitfalls for sure, but for the nature of what I was working to achieve it gets the job done.
Next.js happens to be my favorite framework to build software on the web, mostly because I find the developer experience to be fantastic. Next makes it simple to create dynamic routes for web applications, which is how I managed to separate game clients. Upon creating/joining a lobby and choosing a display name for the game, players are directed to the dynamic slug /[lobby_code]/[player_name] which will prime the board game to the respective client.
A boggle_player table is created to track player stats like word count and score after choosing a display name. This is necessary for the logic to differentiate between game clients and track player respective data for the game over screen.
Real Time Updates
This is all very straightforward and seems simple enough to create, but I run into a major problem when thinking about how to get all of this to update on each client as needed so that changes are rendered to all players, not just the client that directs the change.
I have used Supabase as my go-to option for setting up a database. The software is open source if you wanted to operate it in a closed environment, but for the sake of what I am doing I opted to use their hobby(free) tier as it is very generous. Supabase has great documentation to get started making a piece of CRUD.
Luckily after a quick google search, I found out that Supabase offers the functionality of real-time updates within their free plan! This seemed very generous to me, and after viewing the docs I was shocked to see how easy it looked to incorporate.
const channels = supabase.channel('custom-all-channel')
.on(
'postgres_changes',
{ event: '*', schema: 'public', table: 'boggle_game' },
(payload) => {
console.log('Change received!', payload)
}
)
.subscribe()
The code above creates a channel in which Supabase will listen to any changes from the table: "boggle_game".
- The property, "event:" determines what the channel listens for (Insert, Update, Delete, or all - *). This makes it super simple to determine what to listen for and where to listen for it.
- The payload contains the new and old values from before/after the change to the DB table
From here you can either extract the change from the payload or run a function to fetch the updated data from the table. Now, I incorporated a timer to the board game so that when the timer is done, an endGame() function is run that updates the game_status in the DB table which triggers the real-time update. With this done, the game was multiplayer ready!
Under the hood
When building real-time applications, there are a few ways to keep clients updated when data changes. The most common approaches are database polling and WebSockets, but they work very differently.
Database Polling
Polling is a straightforward but inefficient way to check for updates. The client repeatedly sends requests to the server at fixed intervals (e.g., every second) to check if the data has changed. While this works, it comes with drawbacks:
- Unnecessary load– The server handles requests even when no data has changed.
- Laggy updates – The UI only refreshes when the next request is made, making real-time interactions feel slow.
- Scalability issues – More users mean more requests, which can quickly become costly and inefficient.
Web Sockets & Supabase’s Real-Time Setup
Instead of polling, a better approach is Web Sockets, which provide a persistent, two-way communication channel between the client and server. Once a Web Socket connection is established, the server can push updates to clients instantly, without waiting for them to ask.
PostgreSQL, the database behind Supabase, doesn’t have native Web Socket support. However, Supabase bridges this gap using Replication and real time engine:
- PostgreSQL’s Logical Replication – Whenever a change happens in a table (insert, update, delete), PostgreSQL emits a “change event.”
- Supabase Real time – This service listens to PostgreSQL’s replication stream and forwards those events to an internal Web Socket server.
- Web Socket Communication – Any client subscribed to real-time updates receives the changes immediately as soon as they happen in the database.
With this setup, my Boggle game doesn’t need to spam the server with requests. Instead, each client opens a Web Socket connection and gets notified only when something actually changes—whether a player joins, a word is submitted, or the game ends. This makes the game feel instant while keeping performance smooth, even with multiple players in a lobby.
Conclusion
Overall, I learned a lot from this project which is the underlying goal of any of my hobby projects. The end result is an online replica of a popular word game that my family loves playing. My family is now able to play any time and any place, allowing us to keep up with our traditions from anywhere. I learned what web sockets are, how web socket connections differ from other solutions like database polling, and how to implement such a solution with the help of Supabase. This project was really rewarding to work on, and I look forward to seeing what else I can build with web sockets in the future.
Check out the game below:
Clif's Boggle
While the game does support multiplayer lobbies, you can also enjoy playing by yourself to see how many words you can get in 3 minutes. Enjoy :)
Top comments (0)