<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kehinde Giwa</title>
    <description>The latest articles on DEV Community by Kehinde Giwa (@kenny-204).</description>
    <link>https://dev.to/kenny-204</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3923121%2F6eec22bd-a2b0-4979-88f7-bb8db30e48d5.jpg</url>
      <title>DEV Community: Kehinde Giwa</title>
      <link>https://dev.to/kenny-204</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kenny-204"/>
    <language>en</language>
    <item>
      <title>How I built a search engine</title>
      <dc:creator>Kehinde Giwa</dc:creator>
      <pubDate>Sat, 13 Jun 2026 09:18:51 +0000</pubDate>
      <link>https://dev.to/kenny-204/how-i-built-a-search-engine-54j</link>
      <guid>https://dev.to/kenny-204/how-i-built-a-search-engine-54j</guid>
      <description>&lt;p&gt;Hello all, I built a search engine from scratch in Java. In a previous course we were given it as a group project, but since I worked on just the document processing module, I didn't really understand (I didn't understand at all actually) how the rest of it worked, so I decided to rebuild it from scratch. I called it Nova Search. Keep in mind this post won't have a lot of code but I'll try to keep it as technical as possible.&lt;/p&gt;

&lt;p&gt;Before jumping in — what is a search engine? It's a program that allows users to retrieve information from a source. That source could range from a specific set of documents to the entire internet (e.g. Google). I built mine to cover a specific set of documents, since I don't have the resources to crawl the entire internet lol. The aim was just to understand how search engines work under the hood. So let's get into it.&lt;/p&gt;

&lt;p&gt;I'll divide this into two parts: indexing and querying. Indexing covers what happens to a document and how it gets stored. Querying covers how information is retrieved from the stored documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The indexing
&lt;/h2&gt;

&lt;p&gt;In a search engine, the main data structure for storing data is something called an inverted index. An inverted index maps words, numbers, or terms directly to the specific documents where they appear — it flips the traditional relationship (i.e. documents to words).&lt;/p&gt;

&lt;p&gt;Why do we need it? Imagine searching for the word "dog" in a document with a million words. You'd have to go through every single word and match them — that's O(n) time complexity, meaning the time taken increases with the number of words you have to search through. Now imagine doing that across many documents. It gets inefficient fast.&lt;/p&gt;

&lt;p&gt;Let me show how an inverted index fixes this. Start with two documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Doc1&lt;/strong&gt; → "The dog chased the dog around the yard, the dog barked"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doc2&lt;/strong&gt; → "The fox jumped over the fence and ran away"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To turn this into an inverted index, we create a dictionary — let's call it Index. For each word in each document, we check if it's already in Index. If it's not, we add the word as a key with an array containing the document ID as the value. If it is, we just append the document ID to the existing array.&lt;/p&gt;

&lt;p&gt;Which gives us something like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tbody&gt;
&lt;tr&gt;
    &lt;th&gt;Word&lt;/th&gt;
    &lt;th&gt;Documents&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;the&lt;/td&gt;
&lt;td&gt;[Doc1, Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;dog&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;chased&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;around&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;yard&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;barked&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;fox&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;jumped&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;over&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;fence&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;and&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;ran&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;away&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So now if we wanted to search for the word "dog", we simply check Index["dog"] and it gives us every document that contains it. That's an O(1) operation — no matter how many words were in the document, finding a word always takes constant time.&lt;/p&gt;

&lt;p&gt;Now we could call it a day and be done with our index, but there's a way to make it more efficient: normalizing the text before indexing. Normalizing basically means cleaning the data to remove inconsistencies and bring everything to a single, predictable form. Here's what that looks like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strip punctuation (yes, I'm aware there are no punctuations in this example but 🐻 with me)&lt;/li&gt;
&lt;li&gt;Lowercase everything&lt;/li&gt;
&lt;li&gt;Remove stop words — filler words that don't carry much meaning. You can grab a stop word list online; for our example we'll use ["the", "at", "a", "and"]&lt;/li&gt;
&lt;li&gt;Reduce each word to its root. "Jumped" becomes "jump", "barked" becomes "bark" — that way "barking" and "jumping" won't register as different words. You can do this with a stemmer or a lemmatizer; a stemmer is simpler, a lemmatizer is more accurate but uses more memory. I went with a stemmer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's our cleaned-up inverted index:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tbody&gt;
&lt;tr&gt;
    &lt;th&gt;Word&lt;/th&gt;
    &lt;th&gt;Documents&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;dog&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;chase&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;yard&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;bark&lt;/td&gt;
&lt;td&gt;[Doc1]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;fox&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;jump&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;fenc&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;ran&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;away&lt;/td&gt;
&lt;td&gt;[Doc2]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The querying
&lt;/h2&gt;

&lt;p&gt;Now that we know how to build our index, how do we retrieve information from it based on a query?&lt;/p&gt;

&lt;p&gt;Let's walk through an example. The user searches for "A dog and a fox".&lt;/p&gt;

&lt;p&gt;First we go through the same pipeline we used during indexing — normalize the query, strip punctuation, lowercase, remove stop words, and stem.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tbody&gt;
&lt;tr&gt;
    &lt;th&gt;Step&lt;/th&gt;
    &lt;th&gt;Result&lt;/th&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;Original query&lt;/td&gt;
&lt;td&gt;A dog and a fox&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;Strip punctuation&lt;/td&gt;
&lt;td&gt;A dog and a fox&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;Lowercase&lt;/td&gt;
&lt;td&gt;a dog and a fox&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;Remove stop words&lt;/td&gt;
&lt;td&gt;dog fox&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
&lt;td&gt;Stem&lt;/td&gt;
&lt;td&gt;dog fox&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That gives us "dog fox".&lt;/p&gt;

&lt;p&gt;Now for each word, we check the inverted index for the documents that contain it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"dog" → [Doc1]&lt;/li&gt;
&lt;li&gt;"fox" → [Doc2]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We first take the intersection — the documents common to both. Since there's no intersection in this example, we fall back to the union: all documents that contain any of the words, which gives us [Doc1, Doc2].&lt;/p&gt;

&lt;p&gt;Now that we have our matching documents, how do we decide which one to show first? How do we determine which is most relevant to the user? This is where a ranking algorithm comes in. There are several to choose from, but for a simple search engine I went with TF-IDF.&lt;/p&gt;

&lt;p&gt;TF-IDF stands for term frequency–inverse document frequency. It scores how relevant a word is to a given document.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Term frequency (TF)&lt;/strong&gt; = number of times the word appears in the document ÷ total number of words in that document&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inverse document frequency (IDF)&lt;/strong&gt; = log(total number of documents ÷ number of documents that contain the word)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TF-IDF&lt;/strong&gt; = TF × IDF&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each document, we calculate the TF-IDF of each word in the query:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Doc1&lt;/strong&gt; → dog: 0.5 × 0.301 = 0.1505, fox: 0 × 0.301 = 0 → total: &lt;strong&gt;0.1505&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doc2&lt;/strong&gt; → dog: 0 × 0.301 = 0, fox: 0.2 × 0.301 = 0.0602 → total: &lt;strong&gt;0.0602&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We sort the documents by score and present the results — Doc1 ranks higher, which makes sense, it's the dog document.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwstl4u97jxpg5xbb3l4k.png" alt="Indexing and Querying flow" width="782" height="1020"&gt;
&lt;/h2&gt;

&lt;p&gt;There's one more feature I added — autocompletion — but I think I'll save that for the next article.&lt;/p&gt;

&lt;p&gt;While I was mid-building this project I realized it was following a software architecture pattern I studied last semester — pipe and filter, where data gets transformed at each stage and passed along. I'd understood it well enough to pass the exam , but seeing it click into place in something I actually built hit different. Funny how that works.&lt;/p&gt;

&lt;p&gt;You can check out the code in the repo &lt;a href="https://github.com/Kenny-204/Search-Engine" rel="noopener noreferrer"&gt;here&lt;/a&gt;, and the live link &lt;a href="https://nova-search-eight.vercel.app/" rel="noopener noreferrer"&gt;here&lt;/a&gt; also my portfolio is &lt;a href="https://kennyg.vercel.app/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>I Built an Online Multiplayer Ludo Game</title>
      <dc:creator>Kehinde Giwa</dc:creator>
      <pubDate>Sun, 10 May 2026 14:27:19 +0000</pubDate>
      <link>https://dev.to/kenny-204/i-built-an-online-multiplayer-ludo-game-5dok</link>
      <guid>https://dev.to/kenny-204/i-built-an-online-multiplayer-ludo-game-5dok</guid>
      <description>&lt;p&gt;So, this year I made a few resolutions — and sure, no one keeps to resolutions, but I decided to at least keep these two: first was no more tutorials, and the other was to build projects instead.&lt;br&gt;
I was looking to improve my backend skills and practice advanced backend concepts I hadn't touched before, so I decided to get my hands dirty and make an online multiplayer game.&lt;br&gt;
I initially decided on chess, but after a few minutes of planning out the move logic and rules, I quickly realized I'd spend a lot more time trying to get the piece movements to work and lose sight of my actual goal — or even more likely, just give up. The alternative was a chess validation library, which didn't sound as fun as building from scratch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hard enough that it gave me just enough headache to build but not so much that i'd give up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I decided on Ludo. Let me go over my process.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Frontend
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Building the board
&lt;/h5&gt;

&lt;p&gt;So the first thing I worked on was the ludo board — not the landing page or the auth or player selection, the ludo board. I wanted to get the game working first then just plug and play later. I used Tailwind to first implement the outer parts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed71s3she1bkhu9cw6wb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed71s3she1bkhu9cw6wb.png" alt="Ludo board" width="770" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Tracking tiles.
&lt;/h5&gt;

&lt;p&gt;Then I thought about the tiles which the pieces were going to move on. Sure, I could do some CSS magic to make the grid, but how was I going to be able to track where the piece was? So, I did a thing : I made a function that took a position and returned a style for it. The function was long and there was probably a better way to write it that I didn’t know, but it worked. Each tile now represented a certain position on the board. I kept this function in a utils folder and called it getCellPosition.&lt;/p&gt;

&lt;h5&gt;
  
  
  Pieces, dice, and a happy npm find
&lt;/h5&gt;

&lt;p&gt;After I was done with the tiles, I designed the pieces and set them to their starting positions with another function  . I also needed a dice component. After a quick search on npm I saw a 3D dice box package &lt;a href="https://github.com/3d-dice/dice-box" rel="noopener noreferrer"&gt;3d-dice&lt;/a&gt;, which completely fit my use case, but it turned out it didn’t have type definitions. I initially planned on writing my own .d.ts file, but I realized that the fact that I thought to do that meant someone else probably did too, so I went through the forks of the project and sure enough, I saw a fully typed version.&lt;/p&gt;

&lt;h5&gt;
  
  
  GAME LOGIC
&lt;/h5&gt;

&lt;h6&gt;
  
  
  Implementing the gameplay
&lt;/h6&gt;

&lt;p&gt;This was arguably the hardest part of the entire project: implementing the gameplay. I wouldn’t want to bore you with the intricate details, but I’ll give a high-level overview.&lt;/p&gt;

&lt;h5&gt;
  
  
  useReducer as the game engine
&lt;/h5&gt;

&lt;p&gt;I decided to use a useReducer to handle the game state, since I knew a game would have lots of state interacting with each other. I had an initial game state object which contained an array of player objects, each with an array of piece objects.&lt;br&gt;
I had 3 reducer actions: ROLL_DICE, SELECT_NUMBER, and MOVE_PIECE. The way I visualized it was: when you play a game of ludo, you roll the dice, select a number in your head, and move a piece — so I built my reducer actions around that.&lt;/p&gt;

&lt;h5&gt;
  
  
  Flags → State Machine
&lt;/h5&gt;

&lt;p&gt;Before I get into a summary of what each action did, I’d like to mention an early early decision I made. I decided to manage the piece state using flags — something I had read about earlier, so I just went with them. I had a flag for when a piece was in its home, one for when it’s on the board, one for when it’s in the home stretch, and one for when it’s finished.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This caused a big problem later on, because I now had to reset all the other flags when changing one. I fixed that with a state machine — each piece can only have one state at a time&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F166x7v4v88o66tr69gg0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F166x7v4v88o66tr69gg0.png" alt="State machine" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  The reducer actions
&lt;/h5&gt;

&lt;p&gt;So back to the reducer actions: the ROLL_DICE action, which probably sounds misleading now that I think about it, was basically what happened after you clicked roll dice. It didn’t trigger the 3D dice box — that was done by the onClick handler. It just set the roll result to the result of the 3D dice rolling. It also determined whether the current player was able to move based on the result of the dice roll, or whether the player’s turn was to be skipped (&lt;a href="https://www.ymimports.com/pages/how-to-play-ludo" rel="noopener noreferrer"&gt;Ludo game rules&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Then the main headache was the MOVE_PIECE action. This action handled all piece movement — from getting out of the starting position to capturing pieces and entering the homestretch, even finishing. That was all in the reducer and the game was working fine. Then I proceeded to work on the UI — the home page, the player selection screen, up to the game itself. Then I moved over to the backend.&lt;/p&gt;

&lt;h4&gt;
  
  
  THE BACKEND
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Express, Redis &amp;amp; WebSockets
&lt;/h5&gt;

&lt;p&gt;Working on the backend, I set up my Express app using a new folder structure I saw a friend of mine use. Normally I organize my files into the MVC architecture (model, view, controller), but he organized his into features, where each feature had a controller, service, and router. Then outside the features there was a core folder containing things like errors (custom errors), middlewares, and utils (functions that aren’t scoped to any one feature or service).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffj3ood9apfd6w36wwm5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffj3ood9apfd6w36wwm5t.png" alt="Folder structure" width="800" height="731"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Authentication first
&lt;/h5&gt;

&lt;p&gt;I started out by implementing authentication as I normally do — JWT cookies and all. Then I started on the online game.&lt;/p&gt;

&lt;h5&gt;
  
  
  Reusing the reducer over WebSockets
&lt;/h5&gt;

&lt;p&gt;Remember the big reducer function from earlier? Since a reducer is really just a pure TypeScript function that takes input and returns output, I could reuse the exact same function here using WebSockets.&lt;/p&gt;

&lt;p&gt;If you don't know how WebSockets work, check it out &lt;a href="https://www.geeksforgeeks.org/web-tech/what-is-web-socket-and-how-it-is-different-from-the-http/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The analogy I use: email vs. a phone call. An email is sent and you wait for a reply, while a phone call lets both parties communicate in real time. Sockets have events — you emit events and act on them. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t21k4pxge9gyb1fe67x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3t21k4pxge9gyb1fe67x.png" alt="web sockets" width="672" height="562"&gt;&lt;/a&gt;&lt;br&gt;
I had the event handlers on the server, and each handler received an event from the client, processed it, and emitted its own event back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dxe85hy1w3o5xw6ihse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dxe85hy1w3o5xw6ihse.png" alt="event handlers" width="800" height="871"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  The 7 socket events
&lt;/h5&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Event&lt;/th&gt;
      &lt;th&gt;What it does&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;create-room&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;
        Server initializes a new game state, adds the first player,
        generates a room code and stores it in Redis prefixed with
        &lt;code&gt;"game:"&lt;/code&gt; e.g. &lt;code&gt;"game:001"&lt;/code&gt;.
      &lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;join-room&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;
        Receives the room code, checks if the game exists on Redis
        and is still available to join, then adds the new player.
      &lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;start-game&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;
        Confirms that all the players are ready and sets the game
        state to in-progress.
      &lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;ready-player&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Sets the player’s state to ready.&lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;game-action&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;
        Takes a reducer action emitted from the client, runs it
        through the reducer function, and updates the new game
        state to Redis.
      &lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;rejoin-game&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;
        Allows a disconnected player to reconnect to an ongoing game.
      &lt;/td&gt;
    &lt;/tr&gt;

    &lt;tr&gt;
      &lt;td&gt;&lt;code&gt;disconnect&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;
        Handles cleanup when a player drops.
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h5&gt;
  
  
  Deployment &amp;amp; closing thoughts
&lt;/h5&gt;

&lt;p&gt;So after this I was mostly done with the server and went back to the client. I first implemented authentication with my auth context and provider, then I created a socket provider and a game state provider. The socket provider simply connected to the socket on the backend and broadcast to all the components, while the game state provider held the game state and listened for any incoming socket events from the server and handled them. Then all that was left was to finish up the frontend and the app was ready. I created a container with Docker and deployed on Vercel, Render, and Upstash — and that was the end of my project.&lt;br&gt;
So this was a fun project to build and I learned a lot from it, not just because the game works, but because of what breaking it taught me. The flags-to-state-machine refactor was probably the most valuable thing I took away: sometimes the first design decision that seems reasonable becomes the thing that quietly makes everything harder. I’m currently working on my next project which is a search engine (not a web crawler lol), with Java Spring Boot, so see you then!&lt;br&gt;
If you have any questions or feedback, drop them in the comments — I'd love to hear how you'd have approached it differently.&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/Kenny-204/ludoly" rel="noopener noreferrer"&gt;Github&lt;/a&gt;    Live demo: &lt;a href="https://ludoly.vercel.app" rel="noopener noreferrer"&gt;https://ludoly.vercel.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>mongodb</category>
      <category>redis</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
