<?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: Harshal Patel</title>
    <description>The latest articles on DEV Community by Harshal Patel (@harshal_patel_e3124fa33c6).</description>
    <link>https://dev.to/harshal_patel_e3124fa33c6</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%2F2407162%2F5cf75bb9-5ddd-4dc9-818a-0c3b2f8de190.png</url>
      <title>DEV Community: Harshal Patel</title>
      <link>https://dev.to/harshal_patel_e3124fa33c6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/harshal_patel_e3124fa33c6"/>
    <language>en</language>
    <item>
      <title>Stop Writing Validation Twice: How I Built a "Shared Brain" Sync Engine with Go &amp; WASM</title>
      <dc:creator>Harshal Patel</dc:creator>
      <pubDate>Wed, 31 Dec 2025 12:47:34 +0000</pubDate>
      <link>https://dev.to/harshal_patel_e3124fa33c6/stop-writing-validation-twice-how-i-built-a-shared-brain-sync-engine-with-go-wasm-2hdc</link>
      <guid>https://dev.to/harshal_patel_e3124fa33c6/stop-writing-validation-twice-how-i-built-a-shared-brain-sync-engine-with-go-wasm-2hdc</guid>
      <description>&lt;p&gt;We have all been there.&lt;/p&gt;

&lt;p&gt;You spend Monday writing complex form validation logic in JavaScript for your frontend. You check for email formats, negative numbers, and required fields. Then, you spend Tuesday writing the &lt;em&gt;exact same logic&lt;/em&gt; in Go (or Python/Node) for your backend API.&lt;/p&gt;

&lt;p&gt;The problem? Six months later, you update the backend rule but forget the frontend. Suddenly, your UI says "Success!" but your API throws a &lt;code&gt;400 Bad Request&lt;/code&gt;. Users are confused. You are frustrated.&lt;/p&gt;

&lt;p&gt;I got tired of this "Two-Language Drift." I wanted a way to write my business logic &lt;strong&gt;once in Go&lt;/strong&gt; and have it run everywhere—on the server and inside the user's browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, I built GoSync.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Shared Brain" Architecture
&lt;/h2&gt;

&lt;p&gt;The idea was simple but ambitious: &lt;em&gt;What if the browser was just another Go node?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;By compiling Go to WebAssembly (WASM), I realized I could run the &lt;strong&gt;exact same structs and validation functions&lt;/strong&gt; on the client side.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Old Way (Duplication Hell):
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Frontend (JS) - Maintained by Team A&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&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="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Title required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... more logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Backend (Go) - Maintained by Team B&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;ValidateTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;Task&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;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&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;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;"required"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// ... logic duplicated&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The GoSync Way:
&lt;/h3&gt;

&lt;p&gt;I defined a &lt;code&gt;Syncable&lt;/code&gt; interface. You write your logic once in a shared Go package. The server runs it natively; the browser runs it via WASM.&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;// shared/logic.go&lt;/span&gt;
&lt;span class="c"&gt;// Compiled to BOTH Native Binary and WASM&lt;/span&gt;
&lt;span class="k"&gt;func&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;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Task&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;// This code runs in your Chrome tab AND your Linux server&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&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;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;"required"&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;h2&gt;
  
  
  The Hard Part: &lt;code&gt;syscall/js&lt;/code&gt; and Merkle Trees
&lt;/h2&gt;

&lt;p&gt;Getting Go to talk to the browser isn't exactly plug-and-play yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Bridge (Go ↔ JS)
&lt;/h3&gt;

&lt;p&gt;I had to heavily utilize &lt;code&gt;syscall/js&lt;/code&gt; to bridge the Go runtime with the browser's IndexedDB. The biggest challenge was the &lt;strong&gt;single-threaded nature of WASM&lt;/strong&gt;. If you aren't careful, a heavy Go routine can deadlock the browser UI.&lt;/p&gt;

&lt;p&gt;I ended up writing a custom async wrapper to handle the I/O without freezing the DOM.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Sync Protocol (Merkle Trees)
&lt;/h3&gt;

&lt;p&gt;Sending the whole database back and forth is a bandwidth killer. To make this "Local-First," I implemented a &lt;strong&gt;Merkle Tree synchronization protocol&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Both the client (IndexedDB) and Server (SQLite) maintain a hash tree of their data.&lt;/li&gt;
&lt;li&gt;When they connect, they compare the &lt;strong&gt;"Root Hash."&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If it matches? &lt;strong&gt;Zero data sent.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;If it differs? They drill down the branches to find exactly which record changed (the &lt;strong&gt;delta&lt;/strong&gt;) and swap only that item.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's efficient, fast, and mathematically guarantees consistency.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────┐
│              Root Hash                  │
│  (Changes if ANY child changes)         │
└───────────────────┬─────────────────────┘
                    │
        ┌───────────┴───────────┐
        ▼                       ▼
   ┌─────────┐             ┌─────────┐
   │ Branch A│             │ Branch B│
   └────┬────┘             └────┬────┘
        │                       │
    ┌───┴───┐               ┌───┴───┐
    ▼       ▼               ▼       ▼
 [Leaf1] [Leaf2]         [Leaf3] [Leaf4]
  ✓ Same  ✓ Same         ✗ DIFF!  ✓ Same
                          ↑
                    Only this syncs!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The "Ah-Ha" Moment: Dogfooding
&lt;/h2&gt;

&lt;p&gt;I didn't want to just write a library; I wanted to &lt;strong&gt;prove it works&lt;/strong&gt;. So I built the documentation website using the engine itself.&lt;/p&gt;

&lt;p&gt;When you visit the site, it actually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Downloads the WASM binary (~1.2MB)&lt;/li&gt;
&lt;li&gt;Boots up the Go engine in your browser&lt;/li&gt;
&lt;li&gt;Connects to a test server via WebSocket&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can type in one box (Client A), and watch it sync to the "Server" visualization in &lt;strong&gt;real-time&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;You can even simulate &lt;strong&gt;"Offline Mode"&lt;/strong&gt; on the site. You'll see the data get queued locally in IndexedDB, and the second you toggle "Online," the Merkle sync kicks in and flushes the queue.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it out (and roast my code)
&lt;/h2&gt;

&lt;p&gt;GoSync is currently in &lt;strong&gt;Public Beta (v1.0)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I am specifically looking for feedback on my &lt;code&gt;syscall/js&lt;/code&gt; implementation. I suspect there might be a performance bottleneck there during high-throughput writes, and I'd love to hear from other Go WASM hackers.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 Links
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live Demo &amp;amp; Docs:&lt;/strong&gt; &lt;a href="https://gosync-zero.vercel.app" rel="noopener noreferrer"&gt;gosync-zero.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/HarshalPatel1972/GoSync" rel="noopener noreferrer"&gt;github.com/HarshalPatel1972/GoSync&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🔌 &lt;strong&gt;True Offline&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Works without internet using IndexedDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚡ &lt;strong&gt;Delta Sync&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Merkle Trees ensure only changed data is transferred&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔒 &lt;strong&gt;Self-Hosted&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;No third-party cloud, your data stays yours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🎯 &lt;strong&gt;Last-Write-Wins&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Simple, predictable conflict resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🌍 &lt;strong&gt;Cross-Platform&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Same Go logic runs on browser (WASM) and server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;Let me know what you think! I'm happy to answer questions about the WASM bridge, the sync protocol, or any architectural decisions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Go, WASM, and too much ☕ by &lt;a href="https://github.com/HarshalPatel1972" rel="noopener noreferrer"&gt;Harshal Patel&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>webassembly</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
