<?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>Forem: Valery Odinga</title>
    <description>The latest articles on Forem by Valery Odinga (@odinga71).</description>
    <link>https://forem.com/odinga71</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%2F3269806%2F93ed8f63-cc94-4c20-ab95-f5f8e462248f.png</url>
      <title>Forem: Valery Odinga</title>
      <link>https://forem.com/odinga71</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/odinga71"/>
    <language>en</language>
    <item>
      <title>Stop Searching, Start Contributing: How GoodFirstGo is Making Open Source Approachable</title>
      <dc:creator>Valery Odinga</dc:creator>
      <pubDate>Tue, 31 Mar 2026 20:22:21 +0000</pubDate>
      <link>https://forem.com/odinga71/stop-searching-start-contributing-how-goodfirstgo-is-making-open-source-approachable-66g</link>
      <guid>https://forem.com/odinga71/stop-searching-start-contributing-how-goodfirstgo-is-making-open-source-approachable-66g</guid>
      <description>&lt;p&gt;Remember the first time you tried contributing to open source?&lt;/p&gt;

&lt;p&gt;If you were like most developers, the experience involved staring at a massive, complex codebase on GitHub, clicking the &lt;code&gt;Issues&lt;/code&gt; tab, and immediately feeling overwhelmed. The ecosystem is massive, and while hundreds of maintainers out there are actively asking for help, finding those rare "beginner-friendly" issues requires sifting through mountains of bugs and features that require deep domain knowledge.&lt;/p&gt;

&lt;p&gt;I realized developers shouldn't have to write custom GitHub API queries or dig through unrelated repositories just to make their first Pull Request.&lt;/p&gt;

&lt;p&gt;That’s exactly why I built &lt;code&gt;GoodFirstGo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What is &lt;code&gt;GoodFirstGo&lt;/code&gt;?&lt;br&gt;
&lt;code&gt;GoodFirstGo&lt;/code&gt; is a CLI tool built entirely in Go that brings the perfect open-source issues directly to your terminal.&lt;/p&gt;

&lt;p&gt;Instead of mindlessly browsing GitHub, you tell the CLI what language you want to work in, and it hands you a curated list of active repositories searching for help with the good-first-issue label.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Features Under the Hood&lt;/strong&gt;&lt;br&gt;
1.Smart Language Filtering: Want to practice your Python? Rust? Go? Just pass a &lt;code&gt;--language&lt;/code&gt; flag.&lt;br&gt;
2.Repository Health Filters: Filter out dead projects by mandating a minimum number of stars (&lt;code&gt;--stars 100&lt;/code&gt;).&lt;br&gt;
3.Recency: Nobody wants to comment on an issue from 2008. The &lt;code&gt;--age&lt;/code&gt; flag ensures you're only looking at issues created recently, this week, or this month.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mode: This is my favorite part. By passing the &lt;code&gt;--learning&lt;/code&gt; flag, the CLI will output specialized tutorials and resources tailored to the specific language of the issue you are about to tackle!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Seeing it in Action&lt;/strong&gt;&lt;br&gt;
Because GoodFirstGo compiles down to a single binary, you don't need any complex runtimes or dependencies to use it. If you have Go installed on your machine, getting started takes ten seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/odingaval/GoodFirstGo/cmd/goodfirstgo@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed globally, you can query GitHub instantly. Let's say I want to find a beginner-friendly Go issue in a repository that has at least 500 stars, and I want to see learning resources for Go before I dive in.&lt;/p&gt;

&lt;p&gt;I just type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash
goodfirstgo &lt;span class="nt"&gt;--language&lt;/span&gt; go &lt;span class="nt"&gt;--stars&lt;/span&gt; 500 &lt;span class="nt"&gt;--learning&lt;/span&gt; &lt;span class="nt"&gt;--limit&lt;/span&gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instantly, I get a clean UI output of 5 high-quality repositories looking for help, right in my terminal window. Add a Github Token to your environment, and it bypasses rate limits entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why build a CLI instead of a website?&lt;/strong&gt;&lt;br&gt;
Modern web apps are great, but the Terminal is where developers live. By keeping the tool inside the CLI environment, you can search for issues, clone the resulting repository, and open your IDE without your hands ever leaving the keyboard.&lt;/p&gt;

&lt;p&gt;Plus, writing a CLI in Go is just incredibly fun. It leverages Go's standard &lt;code&gt;net/http&lt;/code&gt; efficiently alongside Cobra for powerful, responsive shell commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking to Contribute?&lt;/strong&gt;&lt;br&gt;
The absolute best part about &lt;code&gt;GoodFirstGo&lt;/code&gt;? Because it is a tool explicitly designed to get beginners into open-source software, the project itself is heavily monitored for first-time contributors!&lt;/p&gt;

&lt;p&gt;If you've never made an open-source contribution before, cloning the repository and adding a small feature to &lt;code&gt;GoodFirstGo&lt;/code&gt; is the perfect place to start.&lt;/p&gt;

&lt;p&gt;Check out the source code, download the latest binary, or star the repository here: 👉 &lt;code&gt;github.com/odingaval/GoodFirstGo&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let me know what your first Pull Request ends up being!&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>go</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building a Concurrent TCP Chat Server in Go (NetCat Clone)</title>
      <dc:creator>Valery Odinga</dc:creator>
      <pubDate>Tue, 24 Mar 2026 13:05:53 +0000</pubDate>
      <link>https://forem.com/odinga71/building-a-concurrent-tcp-chat-server-in-go-netcat-clone-jc1</link>
      <guid>https://forem.com/odinga71/building-a-concurrent-tcp-chat-server-in-go-netcat-clone-jc1</guid>
      <description>&lt;p&gt;In this project, we built a simplified version of the classic NetCat ("nc") tool — a TCP-based chat server that allows multiple clients to connect, send messages, and interact in real time.&lt;/p&gt;

&lt;p&gt;The goal was not just to recreate a chat system, but to deeply understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TCP networking&lt;/li&gt;
&lt;li&gt;Go concurrency (goroutines &amp;amp; channels)&lt;/li&gt;
&lt;li&gt;State management in concurrent systems&lt;/li&gt;
&lt;li&gt;Client-server architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At its core, the system needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept multiple client connections&lt;/li&gt;
&lt;li&gt;Allow clients to send messages&lt;/li&gt;
&lt;li&gt;Broadcast messages to other clients&lt;/li&gt;
&lt;li&gt;Track when users join or leave&lt;/li&gt;
&lt;li&gt;Handle unexpected disconnects (like Ctrl+C)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;This introduces a key challenge:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;«Multiple clients interacting with shared state at the same time.»&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TCP Server Basics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The server listens for incoming connections using:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;listener, _ := net.Listen("tcp", ":8989")&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then continuously accepts clients:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important concept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Accept()" blocks until a client connects&lt;/li&gt;
&lt;li&gt;Each client is handled in a separate goroutine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows multiple users to connect simultaneously.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Goroutines and Concurrency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each client runs in its own goroutine:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go handleConnection(conn)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One slow client does not block others&lt;/li&gt;
&lt;li&gt;Each connection is handled independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, this introduces a problem:&lt;/p&gt;

&lt;p&gt;«Multiple goroutines modifying shared data can cause race conditions.»&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The Shared State Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We needed to track all connected clients:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;var connections map[net.Conn]string&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But multiple goroutines might:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add clients&lt;/li&gt;
&lt;li&gt;Remove clients&lt;/li&gt;
&lt;li&gt;Broadcast messages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At the same time.&lt;/p&gt;

&lt;p&gt;This can cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data corruption&lt;/li&gt;
&lt;li&gt;Crashes ("fatal error: concurrent map writes")&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Solution 1: Mutex&lt;/p&gt;

&lt;p&gt;One approach is using a mutex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this introduces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complexity&lt;/li&gt;
&lt;li&gt;Potential deadlocks&lt;/li&gt;
&lt;li&gt;Performance bottlenecks&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Solution 2: Channels (The Go Way)&lt;/p&gt;

&lt;p&gt;Instead of sharing memory, we used channels to communicate changes.&lt;/p&gt;

&lt;p&gt;This follows Go’s philosophy:&lt;/p&gt;

&lt;p&gt;«“Do not communicate by sharing memory; share memory by communicating.”»&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;ChatRoom Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We designed a "ChatRoom" struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;ChatRoom&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;chatters&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

    &lt;span class="n"&gt;Register&lt;/span&gt;   &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;
    &lt;span class="n"&gt;Unregister&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;
    &lt;span class="n"&gt;Broadcast&lt;/span&gt;  &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Message&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Only one goroutine manages "chatters" and "history"&lt;/li&gt;
&lt;li&gt;Other goroutines send events via channels&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;The Event Loop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core of the system is the "Run()" method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;Register&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;Unregister&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;Broadcast&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This acts like a central controller.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Handling Events&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Client Join&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask for name&lt;/li&gt;
&lt;li&gt;Add to chatters map&lt;/li&gt;
&lt;li&gt;Send chat history&lt;/li&gt;
&lt;li&gt;Broadcast join message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;cr.chatters[client] = struct{}{}&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;2. Message Broadcast&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Append to history&lt;/li&gt;
&lt;li&gt;Send to all clients except sender
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;cr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chatters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receive&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;3. Client Leave&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove from map&lt;/li&gt;
&lt;li&gt;Close channel&lt;/li&gt;
&lt;li&gt;Broadcast leave message&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Handling Ctrl+C (Unexpected Disconnects)&lt;/p&gt;

&lt;p&gt;When a client presses Ctrl+C:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TCP connection closes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"ReadString()&lt;/code&gt;" returns "io.EOF"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We detect this:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
if err != nil {&lt;br&gt;
    // client disconnected&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And broadcast:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"%s has left the chat"&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Message Flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s how a message travels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client sends message&lt;/li&gt;
&lt;li&gt;Goroutine reads it&lt;/li&gt;
&lt;li&gt;Sends it to "Broadcast" channel&lt;/li&gt;
&lt;li&gt;"Run()" receives it&lt;/li&gt;
&lt;li&gt;Loops through clients&lt;/li&gt;
&lt;li&gt;Sends message to each client’s "receive" channel&lt;/li&gt;
&lt;li&gt;Client writer goroutine prints it&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;Main Concepts were:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;1. TCP is Just a Stream&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything is bytes&lt;/li&gt;
&lt;li&gt;Messages are manually structured ("\n")&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;_&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Blocking is Normal_&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;"Accept()" blocks waiting for connections&lt;/li&gt;
&lt;li&gt;"ReadString()" blocks waiting for input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But only within their goroutine.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;3. Goroutines Enable Concurrency&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lightweight threads managed by Go&lt;/li&gt;
&lt;li&gt;Thousands can run efficiently&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;4. Channels Simplify Concurrency&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid shared memory issues&lt;/li&gt;
&lt;li&gt;Centralize state management&lt;/li&gt;
&lt;li&gt;Create predictable flow&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Challenges Faced&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handling empty messages&lt;/li&gt;
&lt;li&gt;Debugging raw string issues&lt;/li&gt;
&lt;li&gt;Understanding blocking behavior&lt;/li&gt;
&lt;li&gt;Managing client disconnects&lt;/li&gt;
&lt;li&gt;Avoiding race conditions&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;This project goes beyond just building a chat app.&lt;/p&gt;

&lt;p&gt;It revealed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How real-time systems work&lt;/li&gt;
&lt;li&gt;How servers handle multiple users&lt;/li&gt;
&lt;li&gt;How to design safe concurrent programs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In many ways, this is a mini version of real-world systems like chat apps, multiplayer servers, and messaging platforms.&lt;/p&gt;




&lt;p&gt;Building this TCP chat server helped me understand how powerful Go is for concurrent systems.&lt;/p&gt;

&lt;p&gt;By combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TCP networking&lt;/li&gt;
&lt;li&gt;Goroutines&lt;/li&gt;
&lt;li&gt;Channels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we can build scalable, real-time applications with relatively simple code.&lt;/p&gt;




</description>
      <category>backend</category>
      <category>go</category>
      <category>networking</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>What Actually Happens When a Form Hits a Go Server</title>
      <dc:creator>Valery Odinga</dc:creator>
      <pubDate>Wed, 04 Feb 2026 08:31:26 +0000</pubDate>
      <link>https://forem.com/odinga71/what-actually-happens-when-a-form-hits-a-go-server-2ohm</link>
      <guid>https://forem.com/odinga71/what-actually-happens-when-a-form-hits-a-go-server-2ohm</guid>
      <description>&lt;p&gt;I’ve been learning how to build web servers in Go, and just yesterday, I tried connecting a simple HTML form to my backend.&lt;/p&gt;

&lt;p&gt;I expected the flow to be straightforward: user clicks Submit, Go runs my code, output appears. Instead, the page refreshed, my server logs looked fine, but nothing rendered.&lt;/p&gt;

&lt;p&gt;That experience made me realize I didn’t fully understand what actually happens when a form hits a Go server. &lt;br&gt;
This article breaks down that process from the browser sending a request, to Go’s &lt;code&gt;net/http&lt;/code&gt;handlers responding focusing on the clear understanding that finally made things click for me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Happens When You Click “Submit”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Clicking Submit doesn’t run your Go code. It sends an HTTP request.&lt;/p&gt;

&lt;p&gt;The browser packages the form data, chooses a method (usually POST), and sends everything to the server. After that, the browser just waits.&lt;/p&gt;

&lt;p&gt;On the Go side, the server is already running and listening. When the request arrives, &lt;code&gt;net/http&lt;/code&gt;matches the URL to a handler function and calls it. That’s the moment your code actually runs.&lt;/p&gt;

&lt;p&gt;Inside the handler, you read the form values, process them, and write a response using &lt;code&gt;http.ResponseWriter.&lt;/code&gt; Whatever you write there is what the browser receives and displays.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why My POST Route Wasn’t Responding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At one point, my server was running fine. The homepage loaded, and my logs showed that the POST route was being hit. Still, nothing showed up in the browser.&lt;/p&gt;

&lt;p&gt;The issue wasn’t that the handler wasn’t running it was that I wasn’t sending anything back.&lt;/p&gt;

&lt;p&gt;In Go, handling a request and responding to it are two separate steps. If your handler reads the form data but never writes to&lt;code&gt;http.ResponseWriter&lt;/code&gt;, the browser has nothing to display.&lt;/p&gt;

&lt;p&gt;Also the GET and POST aren’t interchangeable. A form that sends a POST request will never hit a GET-only handler, even if the URL looks correct. If the method doesn’t match, your code simply won’t run.&lt;/p&gt;

&lt;p&gt;Once I made sure the request method matched and that I was actually writing a response, things finally started to work.&lt;/p&gt;

&lt;p&gt;In Go,&lt;code&gt;http.ResponseWriter&lt;/code&gt; is how your handler talks back to the browser. Think of it as a pen: whatever you write with it becomes the body of the HTTP response.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;For example:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "Hello, world!")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;w&lt;/code&gt; is the &lt;code&gt;ResponseWriter&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Fprintln(w, ...)&lt;/code&gt; writes text into the response.&lt;/p&gt;

&lt;p&gt;When the handler finishes, the server sends everything you wrote back to the browser.&lt;/p&gt;

&lt;p&gt;If you never write to&lt;code&gt;w&lt;/code&gt;, the request still “works” on the server side, but the user sees… nothing. That’s exactly why my POST requests seemed to do nothing: the handler ran, but didn’t write a response.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ResponseWriter&lt;/code&gt; also lets you set headers, status codes, or even send files. But at its core, it’s just the channel for your server’s response.&lt;/p&gt;

&lt;p&gt;Running a Go server isn’t magic it just listens for requests. Clicking a form’s Submit button sends an HTTP request. Your handler runs when the request matches its URL and method, and &lt;code&gt;http.ResponseWriter&lt;/code&gt; is how you send the output back to the browser.&lt;/p&gt;

&lt;p&gt;Once I understood this simple flow, everything clicked: method mismatches explain why GET worked but POST didn’t, and writing to &lt;code&gt;ResponseWriter&lt;/code&gt; explains why nothing showed up at first.&lt;/p&gt;

&lt;p&gt;If you’re starting with Go web servers, remember: request arrives → handler runs → write a response. Master that, and the rest becomes much easier.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>beginners</category>
      <category>go</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Creating an AI App with Genkit and Gemini: A Beginner’s Guide</title>
      <dc:creator>Valery Odinga</dc:creator>
      <pubDate>Wed, 08 Oct 2025 14:36:25 +0000</pubDate>
      <link>https://forem.com/odinga71/creating-an-ai-app-with-genkit-and-gemini-a-beginners-guide-48md</link>
      <guid>https://forem.com/odinga71/creating-an-ai-app-with-genkit-and-gemini-a-beginners-guide-48md</guid>
      <description>&lt;p&gt;Artificial Intelligence (AI) tools are now easier than ever to build and Genkit by Google makes it surprisingly simple to create, test, and run your own AI-powered applications.&lt;/p&gt;

&lt;p&gt;In this guide, I’ll walk you through how I created a Recipe Generator App using Genkit and Gemini, set up the environment, and explored its interactive Developer UI.&lt;/p&gt;

&lt;p&gt;But wait, what is Genkit Really?&lt;/p&gt;

&lt;p&gt;Genkit is an open-source framework by Google that helps developers build AI agents and workflows quickly and intuitively.&lt;/p&gt;

&lt;p&gt;It integrates seamlessly with Gemini, Vertex AI, and other AI providers, allowing you to focus on your app’s logic instead of boilerplate code or API setup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Setting Up the Environment&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make sure you have the following installed:&lt;/p&gt;

&lt;p&gt;Node.js (v18 or later)&lt;/p&gt;

&lt;p&gt;npm or yarn&lt;/p&gt;

&lt;p&gt;A Gemini API key (you can get one from Google AI Studio&lt;br&gt;
Then create a new folder for your project and install Genkit:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm install genkit @genkit-ai/google-genai&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
That’s it! You now have everything you need to start building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Set Your Gemini API Key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Genkit needs your Gemini API key to connect with the model.&lt;/p&gt;

&lt;p&gt;You can set it temporarily in your current terminal session:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;export GEMINI_API_KEY=&amp;lt;your-api-key&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or, make it permanent by adding it to your shell profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 'export GEMINI_API_KEY=&amp;lt;your-api-key&amp;gt;' &amp;gt;&amp;gt; ~/.bashrc
source ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but I stored mine in a .env file and loaded it with dotenv for better security. That's also another option for you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Create Your First Genkit App&lt;/strong&gt; (Recipe Generator)&lt;br&gt;
Let’s create something fun, a recipe generator that crafts meal ideas based on ingredients.&lt;/p&gt;

&lt;p&gt;Start by creating your project structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir src
touch src/index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import 'dotenv/config';
import { googleAI } from '@genkit-ai/google-genai';
import { genkit, z } from 'genkit';

// Initialize Genkit with the Google AI plugin
const ai = genkit({
  plugins: [googleAI()],
  model: googleAI.model('gemini-2.5-flash', {
    temperature: 0.8,
  }),
});

// Define input schema
const RecipeInputSchema = z.object({
  ingredient: z.string().describe('Main ingredient or cuisine type'),
  dietaryRestrictions: z.string().optional().describe('Any dietary restrictions'),
});

// Define output schema
const RecipeSchema = z.object({
  title: z.string(),
  description: z.string(),
  prepTime: z.string(),
  cookTime: z.string(),
  servings: z.number(),
  ingredients: z.array(z.string()),
  instructions: z.array(z.string()),
  tips: z.array(z.string()).optional(),
});

// Define a recipe generator flow
export const recipeGeneratorFlow = ai.defineFlow(
  {
    name: 'recipeGeneratorFlow',
    inputSchema: RecipeInputSchema,
    outputSchema: RecipeSchema,
  },
  async (input) =&amp;gt; {
    // Create a prompt based on the input
    const prompt = `Create a recipe with the following requirements:
      Main ingredient: ${input.ingredient}
      Dietary restrictions: ${input.dietaryRestrictions || 'none'}`;

    // Generate structured recipe data using the same schema
    const { output } = await ai.generate({
      prompt,
      output: { schema: RecipeSchema },
    });

    if (!output) throw new Error('Failed to generate recipe');

    return output;
  },
);

// Run the flow
async function main() {
  const recipe = await recipeGeneratorFlow({
    ingredient: 'avocado',
    dietaryRestrictions: 'vegetarian',
  });

  console.log(recipe);
}

main().catch(console.error);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This defines a Genkit flow that sends a text prompt to Gemini and returns an AI-generated recipe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Run and Test the Flow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Start your Genkit development server:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;genkit start -- npx tsx --watch src/index.ts&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You’ll see output like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Genkit Developer UI running at http://localhost:4000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Interacting with the Developer UI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open your browser and navigate to &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the Genkit Developer UI, a powerful playground for testing and debugging your AI flows.&lt;/p&gt;

&lt;p&gt;Here’s what you can do inside the Developer UI:&lt;br&gt;
1.&lt;strong&gt;&lt;em&gt;Explore Your Flows&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You’ll see your recipeGenerator flow listed under the Flows tab.&lt;br&gt;
Click it to open an interactive testing panel.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;Provide Inputs&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Enter your ingredients into the input box (for example,&lt;br&gt;
chicken, garlic, tomatoes, basil)&lt;br&gt;
and hit Run Flow.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;View AI Responses&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You’ll get an instant response from Gemini — usually a full recipe suggestion!&lt;br&gt;
The UI also shows structured input/output data, so you can see exactly how your flow behaves.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;Inspect Traces&lt;/em&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Switch to the Traces tab to dive deeper into how your flow executed.&lt;br&gt;
You can monitor latency, errors, and data transformations — great for debugging or optimization.&lt;/p&gt;

&lt;p&gt;You just built your first Genkit app!&lt;/p&gt;

&lt;p&gt;From here, you can:&lt;br&gt;
Add more steps to your flow (like nutrition facts or cooking time)&lt;br&gt;
Integrate it with a web frontend or API endpoint&lt;br&gt;
Explore the Genkit dashboard for real-time monitoring and collaboration&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Blockchain Was Cool, But Superchains Are Cooler</title>
      <dc:creator>Valery Odinga</dc:creator>
      <pubDate>Thu, 17 Jul 2025 08:06:56 +0000</pubDate>
      <link>https://forem.com/odinga71/blockchain-was-cool-but-superchains-are-cooler-3caf</link>
      <guid>https://forem.com/odinga71/blockchain-was-cool-but-superchains-are-cooler-3caf</guid>
      <description>&lt;p&gt;Welcome to the World of Blockchain (No, It’s Not a Fancy Prison 🔗)&lt;/p&gt;

&lt;p&gt;If you’ve heard of Bitcoin or Ethereum, you’ve heard of blockchain—the tech behind them. But what is it? &lt;/p&gt;

&lt;p&gt;Imagine blockchain as a giant, un-cheatable spreadsheet that everyone can see but no one can secretly edit. Instead of living on one computer, it’s copied across thousands, making it nearly impossible to hack. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does this matter?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No more needing a bank to send money &lt;/p&gt;

&lt;p&gt;No sneaky changes—once something’s recorded, it’s there forever &lt;/p&gt;

&lt;p&gt;It’s like digital democracy for money &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How Blockchain Works (Without Making Your Brain Hurt&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s break it down with a pizza analogy  (because everything’s better with pizza).&lt;/p&gt;

&lt;p&gt;You order a pizza (this is your "transaction") &lt;/p&gt;

&lt;p&gt;The pizza shop writes it down in their ledger (a "block") &lt;/p&gt;

&lt;p&gt;They add it to a chain of all past orders (the "blockchain") ⛓️&lt;/p&gt;

&lt;p&gt;Every customer has a copy of this ledger—so if the shop tries to say you didn’t pay, everyone else has proof you did. &lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Boom. That’s blockchain. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;The Problem: Blockchains Can Be Slow and Expensive *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Early blockchains (Bitcoin and old-school Ethereum) are like a single cashier at a packed fast-food joint. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slow?&lt;/strong&gt; Transactions take minutes (or hours) &lt;br&gt;
 &lt;strong&gt;Expensive?&lt;/strong&gt; Fees can cost more than your coffee &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Congested?&lt;/strong&gt; Too many orders = delays &lt;/p&gt;

&lt;p&gt;This is why we needed an upgrade. Enter…&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Superchains: The Blockchain Avengers *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If regular blockchains are &lt;strong&gt;flip phones&lt;/strong&gt; , Superchains are &lt;strong&gt;5G smartphones&lt;/strong&gt;—faster, smarter, and way more powerful.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;What Makes Superchains So Cool? *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Modular Design – Like a buffet where each dish is made by a specialist  (one chain handles security, another speed, etc.).&lt;/p&gt;

&lt;p&gt;Shared Security – Instead of every chain hiring its own bouncer , they share a super-bouncer .&lt;/p&gt;

&lt;p&gt;Instant Communication – Different blockchains can talk seamlessly (like WhatsApp for crypto) &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-World Example: Optimism’s Superchain&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Optimism (a big Ethereum upgrade) is building a Superchain highway , where multiple blockchains run together—faster, cheaper, and way smoother.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Why Should You Care? *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Faster transactions – No more waiting 10 minutes for your coffee payment to go through.&lt;/p&gt;

&lt;p&gt;Lower fees  – Sending crypto could cost pennies instead of dollars.&lt;/p&gt;

&lt;p&gt;More apps, less hassle – Games, social media, and finance apps will work like the internet, but decentralized.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Final Thought: The Future is Super *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Blockchain was the Model T Ford revolutionary but clunky. Superchains? They’re the Tesla of crypto sleek, fast, and ready for mass adoption.&lt;/p&gt;

&lt;p&gt;So, if you thought blockchain was cool… just wait until you see what Superchains can do. &lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
