<?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: Utkarsh Kr. Singh</title>
    <description>The latest articles on DEV Community by Utkarsh Kr. Singh (@utkarshkrsingh1103).</description>
    <link>https://dev.to/utkarshkrsingh1103</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%2F1527045%2Fbad2228b-9160-4dcd-bc75-66c7212929bd.jpg</url>
      <title>DEV Community: Utkarsh Kr. Singh</title>
      <link>https://dev.to/utkarshkrsingh1103</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/utkarshkrsingh1103"/>
    <language>en</language>
    <item>
      <title>Building a Clean YAML Config Parser in Go (Without Viper)</title>
      <dc:creator>Utkarsh Kr. Singh</dc:creator>
      <pubDate>Tue, 17 Mar 2026 09:50:27 +0000</pubDate>
      <link>https://dev.to/utkarshkrsingh1103/building-a-clean-yaml-config-parser-in-go-without-viper-431</link>
      <guid>https://dev.to/utkarshkrsingh1103/building-a-clean-yaml-config-parser-in-go-without-viper-431</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog:&lt;br&gt;
&lt;a href="https://utkarshkrsingh.me/blog/building-a-flexible-yaml-config-parser/" rel="noopener noreferrer"&gt;https://utkarshkrsingh.me/blog/building-a-flexible-yaml-config-parser/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I was working on my project &lt;strong&gt;Snag&lt;/strong&gt;, and like most projects at some point, I had to deal with configuration.&lt;/p&gt;

&lt;p&gt;Initially, I thought about using Viper since it is widely adopted in the Go ecosystem. But after looking into it, it felt like overkill for what I needed. I wasn’t dealing with multiple config sources, env overrides, or complex layering. I just needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A clean YAML config&lt;/li&gt;
&lt;li&gt;Strict parsing (no silent mistakes)&lt;/li&gt;
&lt;li&gt;Some flexibility in how users define commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last requirement turned out to be the most interesting part.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Flexible Command Input
&lt;/h2&gt;

&lt;p&gt;I wanted users to define commands in whichever way felt natural to them.&lt;/p&gt;

&lt;p&gt;Some prefer writing commands as a single string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go run main.go&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Others prefer a more explicit format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;go&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;run&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main.go&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are valid. Both are useful.&lt;/p&gt;

&lt;p&gt;Instead of forcing one format, I decided to support both.&lt;/p&gt;




&lt;h2&gt;
  
  
  Struct Design (Keeping It Simple)
&lt;/h2&gt;

&lt;p&gt;The config structure itself is pretty straightforward:&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="c"&gt;// Root configuration structure&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Config&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;Global&lt;/span&gt; &lt;span class="n"&gt;GlobalConfig&lt;/span&gt; &lt;span class="s"&gt;`yaml:"global"`&lt;/span&gt; &lt;span class="c"&gt;// Global settings applied across all rules&lt;/span&gt;
    &lt;span class="n"&gt;Watch&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;Rule&lt;/span&gt;       &lt;span class="s"&gt;`yaml:"watch"`&lt;/span&gt;  &lt;span class="c"&gt;// List of watch rules&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Global-level configuration&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;GlobalConfig&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;Debounce&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;   &lt;span class="s"&gt;`yaml:"debounce"`&lt;/span&gt; &lt;span class="c"&gt;// Delay before triggering actions (e.g., "500ms")&lt;/span&gt;
    &lt;span class="n"&gt;Ignore&lt;/span&gt;   &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`yaml:"ignore"`&lt;/span&gt;   &lt;span class="c"&gt;// List of directories/files to ignore&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Individual watch rule&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Rule&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;Name&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt;            &lt;span class="s"&gt;`yaml:"name"`&lt;/span&gt;     &lt;span class="c"&gt;// Name of the rule (used for identification/logging)&lt;/span&gt;
    &lt;span class="n"&gt;Patterns&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;          &lt;span class="s"&gt;`yaml:"patterns"`&lt;/span&gt; &lt;span class="c"&gt;// File patterns to watch (e.g., **/*.go)&lt;/span&gt;
    &lt;span class="n"&gt;Run&lt;/span&gt;      &lt;span class="n"&gt;Command&lt;/span&gt;           &lt;span class="s"&gt;`yaml:"run"`&lt;/span&gt;      &lt;span class="c"&gt;// Command to execute (custom type)&lt;/span&gt;
    &lt;span class="n"&gt;Restart&lt;/span&gt;  &lt;span class="kt"&gt;bool&lt;/span&gt;              &lt;span class="s"&gt;`yaml:"restart"`&lt;/span&gt;  &lt;span class="c"&gt;// Whether to restart process on change&lt;/span&gt;
    &lt;span class="n"&gt;Env&lt;/span&gt;      &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`yaml:"env"`&lt;/span&gt;      &lt;span class="c"&gt;// Environment variables for the command&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy here. The interesting part is the &lt;code&gt;Run&lt;/code&gt; field.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why a Custom Type for Command?
&lt;/h2&gt;

&lt;p&gt;If we used a plain &lt;code&gt;[]string&lt;/code&gt;, this would fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go run main.go&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because YAML cannot automatically convert a string into a slice.&lt;/p&gt;

&lt;p&gt;So instead, I introduced a custom type:&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="c"&gt;// Command represents a shell command split into arguments&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to define exactly how YAML values should be interpreted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Custom Unmarshalling (The Core Idea)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&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;Command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;UnmarshalYAML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kind&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c"&gt;// Case 1: Command provided as a single string&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ScalarNode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Split the string into arguments like a shell would&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;shlex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Assign parsed arguments to Command&lt;/span&gt;
        &lt;span class="o"&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;args&lt;/span&gt;

    &lt;span class="c"&gt;// Case 2: Command provided as a list&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SequenceNode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

        &lt;span class="c"&gt;// Decode YAML sequence directly into []string&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="o"&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;arr&lt;/span&gt;

    &lt;span class="c"&gt;// Any other type is invalid&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid type for run: expected string or list"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the flexibility comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the YAML value is a &lt;strong&gt;string&lt;/strong&gt;, we split it into arguments
&lt;/li&gt;
&lt;li&gt;If it is already a &lt;strong&gt;list&lt;/strong&gt;, we use it directly
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key detail here is using &lt;code&gt;shlex.Split&lt;/code&gt; instead of &lt;code&gt;strings.Split&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It correctly handles things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go run "main file.go"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which becomes:&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="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"main file.go"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Defaults: Small Feature, Big Impact
&lt;/h2&gt;

&lt;p&gt;A config system should not force users to define every single field.&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;func&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;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ApplyDefaults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// If debounce is not provided, use a sensible default&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;Global&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Debounce&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&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;Global&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Debounce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"500ms"&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 keeps things user-friendly while still giving control when needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validation: Where Most Bugs Are Prevented
&lt;/h2&gt;

&lt;p&gt;Parsing only tells you that the YAML is &lt;em&gt;valid&lt;/em&gt;. It does not tell you if it is &lt;em&gt;correct&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That is where validation comes in.&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;func&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;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c"&gt;// Ensure at least one watch rule is defined&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&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;Watch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no watch rules defined"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Validate debounce duration format&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;Global&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Debounce&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseDuration&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;Global&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Debounce&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid debounce: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Validate each rule&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&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;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Watch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c"&gt;// Rule must have a name&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rule at index %d is missing a name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Rule must define at least one pattern&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Patterns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rule '%s' must have at least one pattern"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Rule must define a command to run&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"rule '%s' is missing a run command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Required fields are present
&lt;/li&gt;
&lt;li&gt;Values are valid (like duration parsing)
&lt;/li&gt;
&lt;li&gt;Errors are caught early with clear messages
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Strict YAML Parsing (Highly Recommended)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;decoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KnownFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Reject unknown fields instead of ignoring them&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables strict mode.&lt;/p&gt;

&lt;p&gt;If a user writes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;debouce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;500ms&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of silently ignoring it, the parser throws an error.&lt;/p&gt;




&lt;h2&gt;
  
  
  Loading Flow (Putting Everything Together)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfgMgr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ConfigMgr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c"&gt;// Read config file from disk&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfgMgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FilePath&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Create YAML decoder&lt;/span&gt;
    &lt;span class="n"&gt;decoder&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c"&gt;// Enable strict field checking&lt;/span&gt;
    &lt;span class="n"&gt;decoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KnownFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Decode YAML into struct&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;decoder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfgMgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"invalid yaml format: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Apply default values for missing fields&lt;/span&gt;
    &lt;span class="n"&gt;cfgMgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ApplyDefaults&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Validate the final configuration&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cfgMgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"configuration validation failed: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow is intentionally simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read file
&lt;/li&gt;
&lt;li&gt;Decode YAML
&lt;/li&gt;
&lt;li&gt;Apply defaults
&lt;/li&gt;
&lt;li&gt;Validate
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Example Configuration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;debounce&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;500ms&lt;/span&gt;
    &lt;span class="na"&gt;ignore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.git&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;vendor&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node_modules&lt;/span&gt;

&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;server&lt;/span&gt;
      &lt;span class="na"&gt;patterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;**/*.go"&lt;/span&gt;
      &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;go run main.go&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8080"&lt;/span&gt;
          &lt;span class="na"&gt;ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Debugging Tip: Print the Final Config
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;data&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;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarshalIndent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfgMgr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"    "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps verify that parsing, defaults, and transformations are working correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Approach Gets Right
&lt;/h2&gt;

&lt;p&gt;After building this, a few things stood out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You do not always need a heavy library like Viper
&lt;/li&gt;
&lt;li&gt;Custom unmarshalling gives you precise control
&lt;/li&gt;
&lt;li&gt;Supporting flexible input formats improves usability
&lt;/li&gt;
&lt;li&gt;Defaults and validation make the system robust
&lt;/li&gt;
&lt;li&gt;Strict parsing prevents subtle configuration bugs
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;For tools like Snag, this approach hits a good balance between simplicity and control.&lt;/p&gt;

&lt;p&gt;It avoids unnecessary abstraction while still being flexible and safe.&lt;/p&gt;

&lt;p&gt;If your configuration needs are similar—single file, predictable structure, and a bit of flexibility—using &lt;code&gt;yaml.v3&lt;/code&gt; directly is a solid choice.&lt;/p&gt;

</description>
      <category>cli</category>
      <category>go</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Building a Recursive File Watcher in Go using fsnotify</title>
      <dc:creator>Utkarsh Kr. Singh</dc:creator>
      <pubDate>Sun, 15 Mar 2026 18:07:51 +0000</pubDate>
      <link>https://dev.to/utkarshkrsingh1103/building-a-recursive-file-watcher-in-go-using-fsnotify-4pm1</link>
      <guid>https://dev.to/utkarshkrsingh1103/building-a-recursive-file-watcher-in-go-using-fsnotify-4pm1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my blog:&lt;br&gt;
&lt;a href="https://utkarshkrsingh.me/blog/recursive-file-watching-golang-fsnotify/" rel="noopener noreferrer"&gt;https://utkarshkrsingh.me/blog/recursive-file-watching-golang-fsnotify/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my previous blog, I talked about my project &lt;strong&gt;&lt;code&gt;Snag&lt;/code&gt;&lt;/strong&gt; and discussed questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What am I going to change?&lt;/li&gt;
&lt;li&gt;What features am I planning to implement?&lt;/li&gt;
&lt;li&gt;How can I make the system &lt;strong&gt;scalable&lt;/strong&gt;, &lt;strong&gt;testable&lt;/strong&gt;, and &lt;strong&gt;maintainable&lt;/strong&gt;?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But one important question naturally comes up:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Hey Utkarsh, how are you going to detect file creation, modification, or deletion inside a directory?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer lies in how the &lt;strong&gt;operating system handles filesystem events&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example, &lt;strong&gt;Linux&lt;/strong&gt; provides a subsystem called &lt;strong&gt;&lt;a href="https://man7.org/linux/man-pages/man7/inotify.7.html" rel="noopener noreferrer"&gt;inotify&lt;/a&gt;&lt;/strong&gt; which allows programs to monitor filesystem events such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File creation&lt;/li&gt;
&lt;li&gt;File modification&lt;/li&gt;
&lt;li&gt;File deletion&lt;/li&gt;
&lt;li&gt;Directory changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To interact with this subsystem in Go, we can use the package &lt;strong&gt;&lt;a href="https://github.com/fsnotify/fsnotify" rel="noopener noreferrer"&gt;fsnotify&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fsnotify&lt;/code&gt; provides a simple and cross-platform way to watch filesystem events in Go.&lt;/p&gt;

&lt;p&gt;In this article, we will build a &lt;strong&gt;recursive file watcher in Go using fsnotify&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To follow along with this tutorial, you should have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic knowledge of &lt;strong&gt;Go&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Understanding of &lt;strong&gt;goroutines and channels&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Familiarity with &lt;strong&gt;filesystem structures&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Install the dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/fsnotify/fsnotify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why Recursive Watching?
&lt;/h2&gt;

&lt;p&gt;One limitation of &lt;code&gt;fsnotify&lt;/code&gt; is that it &lt;strong&gt;does not watch directories recursively&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This means if you only watch the root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;project/
 ├── main.go
 ├── config/
 │   └── config.yaml
 └── handlers/
     └── user.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only changes in &lt;code&gt;project/&lt;/code&gt; will trigger events.&lt;/p&gt;

&lt;p&gt;Changes inside:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;config/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;handlers/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;will not be detected unless watchers are explicitly added to those directories.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because of this, we must:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Recursively find all directories&lt;/li&gt;
&lt;li&gt;Register watchers on each directory&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is exactly what we will implement.&lt;/p&gt;




&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Collect All Directories Recursively
&lt;/h3&gt;

&lt;p&gt;The first step is to gather &lt;strong&gt;all directories inside the project&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Since &lt;code&gt;fsnotify&lt;/code&gt; does not support recursive watching by default, we must manually collect all directories and register them.&lt;/p&gt;

&lt;p&gt;We will create a function called &lt;strong&gt;&lt;code&gt;getAllDirs&lt;/code&gt;&lt;/strong&gt;.&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="c"&gt;// getAllDirs recursively walks the filesystem starting&lt;/span&gt;
&lt;span class="c"&gt;// from the given root directory and returns all directories.&lt;/span&gt;
&lt;span class="c"&gt;// This is needed because fsnotify does not watch directories&lt;/span&gt;
&lt;span class="c"&gt;// recursively by default.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;getAllDirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;dirs&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;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WalkDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DirEntry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error accessing path %s: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Collect directories only&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;dirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function walks the entire directory tree and returns a slice containing &lt;strong&gt;all directories&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Start the File Watcher
&lt;/h3&gt;

&lt;p&gt;Next, we create a function called &lt;strong&gt;&lt;code&gt;startWatching&lt;/code&gt;&lt;/strong&gt; which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a watcher&lt;/li&gt;
&lt;li&gt;Registers directories&lt;/li&gt;
&lt;li&gt;Listens for filesystem events
&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="c"&gt;// lastEvent keeps track of the last time we processed&lt;/span&gt;
&lt;span class="c"&gt;// an event for a specific file. This helps prevent&lt;/span&gt;
&lt;span class="c"&gt;// duplicate events that can occur in rapid succession.&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lastEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// startWatching starts a filesystem watcher on the&lt;/span&gt;
&lt;span class="c"&gt;// provided directories and listens for file events.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;startWatching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;watcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fsnotify&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWatcher&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;watcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// Goroutine that listens for filesystem events&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&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="c"&gt;// File system events&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&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;watcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Events&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c"&gt;// Ignore permission change events&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fsnotify&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chmod&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c"&gt;// Ignore temporary files (like editor backups)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"~"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c"&gt;// Debounce events that fire too quickly&lt;/span&gt;
                &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&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;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;lastEvent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="n"&gt;exists&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;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Microsecond&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;continue&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;lastEvent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;

                &lt;span class="c"&gt;// Handle file/directory creation&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fsnotify&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;isDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&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;isDir&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Add watcher dynamically to new directory:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Run configured command for file:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c"&gt;// Handle file modifications&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fsnotify&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Modified file:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c"&gt;// Error handling&lt;/span&gt;
            &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&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;watcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Watcher error:"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="c"&gt;// Register watchers for all provided directories&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dir&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;dirs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;watcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dir&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Block forever to keep the program running&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&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;h4&gt;
  
  
  What this function does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Creates a filesystem watcher&lt;/li&gt;
&lt;li&gt;Listens for events inside a goroutine&lt;/li&gt;
&lt;li&gt;Filters unnecessary events&lt;/li&gt;
&lt;li&gt;Handles file creation and modification&lt;/li&gt;
&lt;li&gt;Uses &lt;strong&gt;debouncing&lt;/strong&gt; to avoid duplicate triggers&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. Detect Whether the Event is a File or Directory
&lt;/h3&gt;

&lt;p&gt;When a &lt;strong&gt;Create&lt;/strong&gt; event occurs, we need to determine whether the created entity is a &lt;strong&gt;file or directory&lt;/strong&gt;.&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="c"&gt;// isDirectory checks whether the provided path&lt;/span&gt;
&lt;span class="c"&gt;// refers to a directory.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsDir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function simply checks filesystem metadata to determine if the path is a directory.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Program Entry Point
&lt;/h3&gt;

&lt;p&gt;Finally, everything comes together in the &lt;code&gt;main&lt;/code&gt; function.&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="c"&gt;// Entry point of the program.&lt;/span&gt;
&lt;span class="c"&gt;// It gathers all directories starting from the root&lt;/span&gt;
&lt;span class="c"&gt;// and starts the filesystem watcher.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"."&lt;/span&gt;

    &lt;span class="c"&gt;// Collect all directories recursively&lt;/span&gt;
    &lt;span class="n"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;getAllDirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&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;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Failed to get directories: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Start watching filesystem changes&lt;/span&gt;
    &lt;span class="n"&gt;startWatching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirs&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;h4&gt;
  
  
  Step-by-step flow
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Start from the root directory&lt;/li&gt;
&lt;li&gt;Recursively collect all directories&lt;/li&gt;
&lt;li&gt;Register watchers for each directory&lt;/li&gt;
&lt;li&gt;Listen for filesystem events&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  How the Watcher Works
&lt;/h2&gt;

&lt;p&gt;The overall workflow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project Directory
        ↓
Scan All Directories
        ↓
Register Watchers
        ↓
Listen for Events
        ↓
Filter / Debounce Events
        ↓
Trigger Action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pipeline allows us to efficiently monitor filesystem changes across the entire project.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Result
&lt;/h2&gt;

&lt;p&gt;With this approach, we have built a &lt;strong&gt;recursive file watcher system&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The program can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watch a directory&lt;/li&gt;
&lt;li&gt;Monitor &lt;strong&gt;all subdirectories&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Detect file &lt;strong&gt;creation and modification events&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Filter unnecessary filesystem noise&lt;/li&gt;
&lt;li&gt;Prevent duplicate triggers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This structure can serve as the foundation for tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hot reload systems&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build automation tools&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File synchronization utilities&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Developer productivity tools&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can extend this watcher to trigger commands, run tests, reload servers, or automate workflows whenever files change in your project.&lt;/p&gt;

</description>
      <category>go</category>
      <category>fsnotify</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Snag - Rewrite</title>
      <dc:creator>Utkarsh Kr. Singh</dc:creator>
      <pubDate>Wed, 11 Mar 2026 09:59:43 +0000</pubDate>
      <link>https://dev.to/utkarshkrsingh1103/snag-rewrite-1926</link>
      <guid>https://dev.to/utkarshkrsingh1103/snag-rewrite-1926</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on my website:&lt;br&gt;
&lt;a href="https://utkarshkrsingh.me/blog/snag-rewrite/" rel="noopener noreferrer"&gt;https://utkarshkrsingh.me/blog/snag-rewrite/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the early days when I started learning &lt;code&gt;Go&lt;/code&gt;, I wasn't very familiar with its idioms. Like most beginners, I experimented by building small projects. One of those projects was &lt;code&gt;Snag&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The idea behind &lt;code&gt;Snag&lt;/code&gt; was simple: it watched the current directory and checked whether a file had been modified. If a change was detected, it would automatically compile the code.&lt;/p&gt;

&lt;p&gt;However, there was a major limitation. The compile commands were hard-coded directly into the source code. This made the tool impractical and limited it to only a few languages such as &lt;code&gt;C/C++&lt;/code&gt;, &lt;code&gt;Python&lt;/code&gt;, and &lt;code&gt;Go&lt;/code&gt;. Because of this design choice, the project had several issues and wasn't an ideal implementation.&lt;/p&gt;

&lt;p&gt;At that time I also wasn't familiar with GitHub, so the project never made it there.&lt;/p&gt;

&lt;p&gt;Now things are different. I have spent much more time working with &lt;code&gt;Go&lt;/code&gt;, learning its idioms, and understanding how to write more idiomatic and maintainable code. Because of that, I decided to rewrite the project from scratch.&lt;/p&gt;

&lt;p&gt;So welcome to my blog on &lt;strong&gt;Snag — Rewrite&lt;/strong&gt;, where I will discuss the improvements and design decisions I plan to implement in the new version.&lt;/p&gt;

&lt;h2&gt;
  
  
  Old Snag Abilities
&lt;/h2&gt;

&lt;p&gt;The original version of &lt;code&gt;Snag&lt;/code&gt; did not have many features. Its primary responsibility was simple: detect file modifications and react accordingly.&lt;/p&gt;

&lt;p&gt;But this raises an interesting question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does &lt;code&gt;Snag&lt;/code&gt; detect file changes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To understand this, we need to look at how &lt;code&gt;Linux&lt;/code&gt; tracks file and directory updates.&lt;/p&gt;

&lt;p&gt;Linux provides a subsystem called &lt;strong&gt;&lt;code&gt;inotify&lt;/code&gt;&lt;/strong&gt;, which monitors filesystem events such as file creation, modification, and deletion. Tools and applications can subscribe to these events to react whenever something changes in the filesystem.&lt;/p&gt;

&lt;p&gt;In the Go implementation, I used the package &lt;strong&gt;&lt;code&gt;fsnotify&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This library internally uses the &lt;code&gt;inotify&lt;/code&gt; API on Linux to watch filesystem events. With it, &lt;code&gt;Snag&lt;/code&gt; was able to detect when files were modified and trigger the appropriate action.&lt;/p&gt;

&lt;p&gt;Now let's look at what I plan to improve in the new version.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Plans for the New Version
&lt;/h2&gt;

&lt;p&gt;For the rewrite, I want to make the project &lt;strong&gt;more flexible, testable, and scalable&lt;/strong&gt;. Here are the main ideas I plan to implement.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Decoupling Configuration from Logic
&lt;/h3&gt;

&lt;p&gt;In the previous version, build commands were hard-coded in the source code. This made the tool rigid and difficult to adapt for different projects.&lt;/p&gt;

&lt;p&gt;In the rewrite, I plan to introduce a &lt;strong&gt;configuration file&lt;/strong&gt;. This file will allow users to specify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files or directories to ignore&lt;/li&gt;
&lt;li&gt;Commands to run when files change&lt;/li&gt;
&lt;li&gt;Other project-specific behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can think of it as something similar to a &lt;code&gt;Makefile&lt;/code&gt;. The difference is that instead of manually running commands, &lt;code&gt;Snag&lt;/code&gt; will automatically trigger them when relevant files are modified.&lt;/p&gt;

&lt;p&gt;This allows developers to focus on writing code rather than repeatedly running build commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Concurrency and Context
&lt;/h3&gt;

&lt;p&gt;This time I want to take advantage of one of Go’s strongest features: &lt;strong&gt;concurrency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I plan to run commands using &lt;code&gt;goroutines&lt;/code&gt; so that processes can execute independently. At the same time, I will manage these processes using &lt;code&gt;context&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This approach solves an important problem.&lt;/p&gt;

&lt;p&gt;Imagine a file changes while the previous build process is still running. Instead of tracking PIDs (process IDs) manually, I can simply cancel the running task using the &lt;code&gt;context&lt;/code&gt;. Once the previous process is cancelled, a new one can start immediately.&lt;/p&gt;

&lt;p&gt;This makes the system cleaner and avoids the complexity of manual process management.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Recursive Watching
&lt;/h3&gt;

&lt;p&gt;Another improvement will be &lt;strong&gt;recursive directory watching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At startup, &lt;code&gt;Snag&lt;/code&gt; will walk through the entire directory tree and attach watchers to all subdirectories. Additionally, if a new directory is created while the program is running, a watcher will be added dynamically.&lt;/p&gt;

&lt;p&gt;This ensures that changes anywhere in the project are detected automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;These are the core ideas behind the rewrite of &lt;code&gt;Snag&lt;/code&gt;. My goal is to turn it into a more robust and flexible tool.&lt;/p&gt;

&lt;p&gt;You can think of &lt;code&gt;Snag&lt;/code&gt; as a &lt;strong&gt;language-agnostic hot reloader&lt;/strong&gt;. Instead of being tied to a specific language, it relies on its configuration file to determine what files to watch and what commands to execute.&lt;/p&gt;

&lt;p&gt;Hopefully, this rewrite will not only improve the tool but also make it a solid project to showcase on my resume.&lt;/p&gt;

</description>
      <category>go</category>
      <category>coding</category>
      <category>clitool</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>CORS - Cross Origin Resource Sharing</title>
      <dc:creator>Utkarsh Kr. Singh</dc:creator>
      <pubDate>Wed, 10 Dec 2025 17:39:56 +0000</pubDate>
      <link>https://dev.to/utkarshkrsingh1103/cors-cross-origin-resource-sharing-30j4</link>
      <guid>https://dev.to/utkarshkrsingh1103/cors-cross-origin-resource-sharing-30j4</guid>
      <description>&lt;h2&gt;
  
  
  CORS is one of those things you don’t really learn…
&lt;/h2&gt;

&lt;p&gt;You just run into it.&lt;/p&gt;

&lt;p&gt;Everything works.&lt;br&gt;&lt;br&gt;
API is up.&lt;br&gt;&lt;br&gt;
Backend is responding.&lt;/p&gt;

&lt;p&gt;Then the browser says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Blocked by CORS policy.”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And suddenly you’re questioning your life choices 😄&lt;/p&gt;

&lt;p&gt;For a long time, CORS feels like a bug.&lt;br&gt;&lt;br&gt;
But it’s not.&lt;/p&gt;

&lt;p&gt;It’s actually the browser trying to protect users by saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Hey, are you sure this website should be talking to that server?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It works in Postman but not in the browser
&lt;/li&gt;
&lt;li&gt;The backend logs look fine
&lt;/li&gt;
&lt;li&gt;Adding one missing header magically fixes everything
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And also why &lt;strong&gt;“just disable CORS”&lt;/strong&gt; is a terrible idea&lt;br&gt;&lt;br&gt;
(even though we’ve all Googled it).&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Postman and curl don’t care about CORS
&lt;/h2&gt;

&lt;p&gt;CORS is a &lt;strong&gt;browser-only security rule&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Postman, curl, and other tools aren’t trying to protect end users—they’re just clients making HTTP requests. They don’t block responses based on origin, so they’ll happily show you the data even if the server never sends CORS headers.&lt;/p&gt;

&lt;p&gt;Browsers, on the other hand, sit between your app and the user. If they didn’t enforce CORS, any website could silently read data from another site using your logged-in session.&lt;/p&gt;

&lt;p&gt;That’s the real threat CORS is preventing.&lt;/p&gt;




&lt;h2&gt;
  
  
  And what’s a preflight request?
&lt;/h2&gt;

&lt;p&gt;That mysterious &lt;code&gt;OPTIONS&lt;/code&gt; call you see before your actual API request?&lt;/p&gt;

&lt;p&gt;That’s the browser asking politely first.&lt;/p&gt;

&lt;p&gt;Before sending certain requests (like &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, or requests with custom headers), the browser sends a preflight request to the server saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“Hey, I want to make this request with these methods and headers. Are you okay with that?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the server says yes (with the right CORS headers), the real request is sent.&lt;/p&gt;

&lt;p&gt;If not, the browser stops everything right there.&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%2Fmci3gvqciphscqvo1f4y.png" alt="CORS diagram" width="800" height="398"&gt;
&lt;/h2&gt;

&lt;p&gt;Once it clicks, CORS stops being scary.&lt;/p&gt;

&lt;p&gt;You realize it’s just rules about who can talk to whom, and how explicitly you need to say yes.&lt;/p&gt;

&lt;p&gt;Honestly, understanding CORS felt like a small rite of passage as a developer.&lt;/p&gt;

&lt;p&gt;If CORS has ever ruined your day, welcome to the club.&lt;br&gt;&lt;br&gt;
If you’ve mastered it, you’ve earned your badge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What was your first CORS error like? 👇&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>api</category>
    </item>
    <item>
      <title>🚀 From Clueless to Confident: My First Week with Keploy for API Testing</title>
      <dc:creator>Utkarsh Kr. Singh</dc:creator>
      <pubDate>Fri, 27 Jun 2025 07:06:36 +0000</pubDate>
      <link>https://dev.to/utkarshkrsingh1103/from-clueless-to-confident-my-first-week-with-keploy-for-api-testing-2cpn</link>
      <guid>https://dev.to/utkarshkrsingh1103/from-clueless-to-confident-my-first-week-with-keploy-for-api-testing-2cpn</guid>
      <description>&lt;p&gt;I’ll be honest—I had zero experience with proper API testing until just recently. Sure, I’ve built a few APIs here and there using Go and knew how to check responses in Postman or curl. But writing tests? Especially integration or E2E ones? I always meant to do it... but never really did. Mostly because it felt tedious, confusing, and well, manual.&lt;/p&gt;

&lt;p&gt;That changed this week when I stumbled upon Keploy—an AI-powered API testing tool—and I decided to try it out for a workshop project. Here's what happened 👇&lt;/p&gt;

&lt;h3&gt;
  
  
  🤯 My Usual API Testing (Or Lack of It)
&lt;/h3&gt;

&lt;p&gt;Before Keploy, my “testing strategy” was mostly just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run the backend.&lt;/li&gt;
&lt;li&gt;Hit endpoints manually using Postman.&lt;/li&gt;
&lt;li&gt;Check console logs or database rows to “see if it worked”.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That obviously didn’t scale, and I knew it wasn’t the right way to test things—but setting up full test suites (mocking, database seeding, etc.) felt like a huge barrier. Plus, every time the API changed, I’d have to go update a bunch of test cases by hand. 😵‍💫&lt;/p&gt;

&lt;h3&gt;
  
  
  💡 First Time Using Keploy
&lt;/h3&gt;

&lt;p&gt;Keploy promised “automated test generation” just by using my app normally. Skeptical but curious, I decided to give it a shot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I ran my backend app (a simple anime watchlist built in Go).&lt;/li&gt;
&lt;li&gt;Installed the Keploy CLI and attached it to my app.&lt;/li&gt;
&lt;li&gt;Used the app like a real user—adding, updating, deleting anime records.&lt;/li&gt;
&lt;li&gt;Keploy silently recorded all the API calls and generated test cases and mocks from real traffic!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No writing test functions. No crafting request/response payloads. No environment setup nightmares.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Running the Tests
&lt;/h3&gt;

&lt;p&gt;I then ran keploy test—and to my surprise, it spun up the same traffic I had recorded, validated the responses and even simulated the same DB behavior.&lt;/p&gt;

&lt;p&gt;What blew me away was the coverage: I basically hit a few endpoints, and Keploy had already generated 70–80% test coverage just from that. I wasn’t even trying!&lt;/p&gt;

&lt;p&gt;With a few more user interactions, I pushed it to nearly 100%.&lt;/p&gt;

&lt;h3&gt;
  
  
  😅 What Was Hard
&lt;/h3&gt;

&lt;p&gt;Of course, it wasn’t completely smooth:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Getting Keploy to authenticate with the cloud dashboard took a few tries (pro tip: you need a valid API key from Keploy’s dashboard).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;My app uses Docker-based databases, so I had to configure ports and ensure both read/write databases were accessible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since I was new to this, understanding what was going on “under the hood” took some reading.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But compared to writing tests manually from scratch? This was 10x easier.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ What Excites Me Now
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Confidence in my APIs: Every time I make changes, I can rerun the suite and trust that it’ll catch regressions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Speed: Recording tests while I just use the app is wild. I’m not wasting hours writing repetitive assertions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalability: If I work on a bigger team, everyone benefits from shared test coverage without needing every dev to be a testing expert.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🧠 Final Thoughts (From a Beginner)
&lt;/h3&gt;

&lt;p&gt;I’m still new to API testing, but Keploy made it feel like I wasn’t. It feels more like having a smart assistant watching over your app and creating tests for you, rather than something you need to fight with.&lt;/p&gt;

&lt;p&gt;If you’re like me—someone who avoided testing because it felt like a chore—give Keploy a shot. You’ll go from 0 to hero faster than you think.&lt;/p&gt;

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