<?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.us-east-2.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 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>
