<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Golang on Tachera W Sasi</title><link>https://tacherasasi.github.io/tags/golang/</link><description>Recent content in Golang on Tachera W Sasi</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 28 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tacherasasi.github.io/tags/golang/index.xml" rel="self" type="application/rss+xml"/><item><title>The One-Word Bug That Broke Our Worker for 6 Days</title><link>https://tacherasasi.github.io/posts/the-one-word-bug-that-broke-our-worker-for-6-days-9cadd534ee77/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/the-one-word-bug-that-broke-our-worker-for-6-days-9cadd534ee77/</guid><description>&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*CtVFn53iNGx3lQ2MEe3JKw.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="for-six-days-our-system-was-lying-to-us"&gt;For six days, our system was lying to us&lt;/h3&gt;
&lt;p&gt;Not crashing. Not failing loudly. Just quietly pretending everything was fine while doing absolutely nothing.&lt;/p&gt;
&lt;p&gt;And the cause?&lt;/p&gt;
&lt;p&gt;One wrong word in a shell script.&lt;/p&gt;
&lt;h3 id="everything-looked-fine"&gt;Everything Looked Fine&lt;/h3&gt;
&lt;p&gt;We run a pretty standard setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Go API (core)&lt;/li&gt;
&lt;li&gt;A background worker (minion)&lt;/li&gt;
&lt;li&gt;Redis for queues&lt;/li&gt;
&lt;li&gt;Docker Compose holding it all together&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We recently moved bulk SMS processing into the worker using Asynq. Clean separation, better scalability, the usual win.&lt;/p&gt;
&lt;p&gt;We deployed.&lt;/p&gt;
&lt;p&gt;The API responded perfectly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;200 OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;bulk SMS queued successfully
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Except… no SMS was ever sent.&lt;/p&gt;
&lt;h3 id="the-worker-was-dead-silent"&gt;The Worker Was Dead Silent&lt;/h3&gt;
&lt;p&gt;First instinct: check the worker logs.&lt;/p&gt;
&lt;p&gt;Nothing.&lt;/p&gt;
&lt;p&gt;No errors. No panics. No trace of the task ever being picked up.&lt;/p&gt;
&lt;p&gt;Just one quiet warning after retries were exhausted.&lt;/p&gt;
&lt;p&gt;Meanwhile, everything else worked:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;login alerts ✅&lt;/li&gt;
&lt;li&gt;email notifications ✅&lt;/li&gt;
&lt;li&gt;scheduled jobs ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only bulk SMS failed.&lt;/p&gt;
&lt;p&gt;At this point, we did what engineers always do when the signal is weak:&lt;/p&gt;
&lt;p&gt;We guessed.&lt;/p&gt;
&lt;h3 id="we-investigated-the-wrong-things"&gt;We Investigated the Wrong Things&lt;/h3&gt;
&lt;p&gt;We went deep into the stack:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Suspected Redis corruption&lt;/li&gt;
&lt;li&gt;Questioned queue priorities&lt;/li&gt;
&lt;li&gt;Looked for serialization bugs&lt;/li&gt;
&lt;li&gt;Added panic recovery middleware&lt;/li&gt;
&lt;li&gt;Logged full payloads&lt;/li&gt;
&lt;li&gt;Added startup diagnostics&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We deployed again.&lt;/p&gt;
&lt;p&gt;Still nothing.&lt;/p&gt;
&lt;p&gt;And this is where most people waste days. We almost did.&lt;/p&gt;
&lt;p&gt;Because we were debugging behavior… not reality.&lt;/p&gt;
&lt;h3 id="the-subtle-clue"&gt;The Subtle Clue&lt;/h3&gt;
&lt;p&gt;There was one tiny inconsistency.&lt;/p&gt;
&lt;p&gt;Our worker used to log:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Worker started
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We had changed it to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Worker started (server + scheduler running)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After deploy, the logs still showed the old message.&lt;/p&gt;
&lt;p&gt;That should have been the end of the investigation right there.&lt;/p&gt;
&lt;p&gt;Our code wasn’t running.&lt;/p&gt;
&lt;h3 id="the-real-problem"&gt;The Real Problem&lt;/h3&gt;
&lt;p&gt;The issue wasn’t in Go.&lt;br&gt;
It wasn’t in Redis.&lt;br&gt;
It wasn’t in queues.&lt;/p&gt;
&lt;p&gt;It was in this script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;docker compose build --no-cache api &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;docker compose up -d --remove-orphans&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And this config:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;core&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;minion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Look closely.&lt;/p&gt;
&lt;p&gt;We were building api.&lt;/p&gt;
&lt;p&gt;There is no api.&lt;/p&gt;
&lt;p&gt;Docker didn’t complain. It didn’t warn. It didn’t fail.&lt;/p&gt;
&lt;p&gt;It just did nothing.&lt;/p&gt;
&lt;p&gt;And then docker compose up happily started containers using old cached images.&lt;/p&gt;
&lt;p&gt;So for six days:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The API sometimes rebuilt&lt;/li&gt;
&lt;li&gt;The worker never rebuilt&lt;/li&gt;
&lt;li&gt;The system kept deploying successfully&lt;/li&gt;
&lt;li&gt;Our fixes never actually shipped&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We were debugging code that wasn’t even running.&lt;/p&gt;
&lt;h3 id="the-fix-was-embarrassing"&gt;The Fix Was Embarrassing&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- docker compose build --no-cache api
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+ docker compose build --no-cache core minion
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s it.&lt;/p&gt;
&lt;p&gt;Two words instead of one.&lt;/p&gt;
&lt;p&gt;We deployed again.&lt;/p&gt;
&lt;p&gt;Immediately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New startup logs appeared&lt;/li&gt;
&lt;li&gt;Bulk SMS tasks were picked up&lt;/li&gt;
&lt;li&gt;Jobs executed in milliseconds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;System fixed.&lt;/p&gt;
&lt;h3 id="what-this-actually-teaches"&gt;What This Actually Teaches&lt;/h3&gt;
&lt;h3 id="1-if-your-fix-doesnt-change-behavior-assume-it-never-deployed"&gt;1. If your fix doesn’t change behavior, assume it never deployed&lt;/h3&gt;
&lt;p&gt;Stop overthinking.&lt;/p&gt;
&lt;p&gt;Before touching Redis, queues, or concurrency… ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Is my new binary even running?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you skip this, you deserve the hours you’re about to lose.&lt;/p&gt;
&lt;h3 id="2-silent-failures-are-worse-than-crashes"&gt;2. Silent failures are worse than crashes&lt;/h3&gt;
&lt;p&gt;Crashes force action.&lt;/p&gt;
&lt;p&gt;Silent no-ops pass CI, pass deploys, and waste days.&lt;/p&gt;
&lt;p&gt;docker compose build should not succeed when the service doesn’t exist.&lt;/p&gt;
&lt;p&gt;But it does.&lt;/p&gt;
&lt;p&gt;So you have to defend against that yourself.&lt;/p&gt;
&lt;h3 id="3-your-deployment-pipeline-is-part-of-your-codebase"&gt;3. Your deployment pipeline is part of your codebase&lt;/h3&gt;
&lt;p&gt;People treat scripts like they’re disposable.&lt;/p&gt;
&lt;p&gt;They’re not.&lt;/p&gt;
&lt;p&gt;That one-line deploy.sh caused more damage than any bug in the Go code.&lt;/p&gt;
&lt;p&gt;Review it. Test it. Break it intentionally.&lt;/p&gt;
&lt;h3 id="4-logging-is-not-optional-for-workers"&gt;4. Logging is not optional for workers&lt;/h3&gt;
&lt;p&gt;If your worker can fail quietly, it will.&lt;/p&gt;
&lt;p&gt;You need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;startup fingerprints (commit hash, build time)&lt;/li&gt;
&lt;li&gt;task-level logs&lt;/li&gt;
&lt;li&gt;explicit success/failure visibility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Otherwise, you’re blind.&lt;/p&gt;
&lt;h3 id="5-rename-things-properly-or-dont-rename-them-at-all"&gt;5. Rename things properly or don’t rename them at all&lt;/h3&gt;
&lt;p&gt;api became core.&lt;/p&gt;
&lt;p&gt;The script didn’t.&lt;/p&gt;
&lt;p&gt;That mismatch cost six days.&lt;/p&gt;
&lt;p&gt;Refactors don’t end when the code compiles.&lt;/p&gt;
&lt;h3 id="the-real-lesson"&gt;The Real Lesson&lt;/h3&gt;
&lt;p&gt;This wasn’t a debugging failure.&lt;/p&gt;
&lt;p&gt;It was a discipline failure.&lt;/p&gt;
&lt;p&gt;We trusted the pipeline without verifying it.&lt;br&gt;
We chased complex explanations before ruling out simple ones.&lt;br&gt;
We debugged symptoms instead of checking fundamentals.&lt;/p&gt;
&lt;p&gt;And that’s how a one-word mistake beats experienced engineers.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=9cadd534ee77" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/the-one-word-bug-that-broke-our-worker-for-6-days-9cadd534ee77?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>After Using 10+ Languages, Go Is Still My Favorite for Backend Systems</title><link>https://tacherasasi.github.io/posts/after-using-10-languages-go-is-still-my-favorite-for-backend-systems-9148165fa6dc/</link><pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/after-using-10-languages-go-is-still-my-favorite-for-backend-systems-9148165fa6dc/</guid><description>&lt;p&gt;&lt;img src="https://cdn-images-1.medium.com/max/1024/1*AYRooeSNqJ_pq64abMMcsg.png" alt=""&gt;Golang Fanatic Here&lt;/p&gt;
&lt;h3 id="introduction"&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Over the past few years I’ve experimented with a lot of programming languages.&lt;/p&gt;
&lt;p&gt;PHP, JavaScript, TypeScript, Python, Java, Go, Rust, C, C++, and several others. I enjoy exploring languages because every language teaches you something about how software should be built.&lt;/p&gt;
&lt;p&gt;Some languages are great for rapid prototyping.&lt;br&gt;
Some shine in performance-critical environments.&lt;br&gt;
Others dominate specific ecosystems.&lt;/p&gt;
&lt;p&gt;But when it comes to &lt;strong&gt;building backend systems&lt;/strong&gt; , I keep coming back to Go.&lt;/p&gt;
&lt;p&gt;Not because it’s the most advanced language.&lt;br&gt;
Not because it has the most features.&lt;/p&gt;
&lt;p&gt;Actually, the opposite.&lt;/p&gt;
&lt;p&gt;Go wins because it focuses on &lt;strong&gt;simplicity, performance, and practical tooling&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="1-simplicity"&gt;1. Simplicity&lt;/h3&gt;
&lt;p&gt;One of Go’s biggest strengths is that the language is intentionally small.&lt;/p&gt;
&lt;p&gt;There are no complex generics systems like in C++.&lt;br&gt;
No endless abstractions like in some object-oriented languages.&lt;br&gt;
No massive meta-programming layers.&lt;/p&gt;
&lt;p&gt;At first this feels limiting.&lt;/p&gt;
&lt;p&gt;But after building real systems, you realize something important: &lt;strong&gt;most backend problems don’t need complex language features.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;They need:&lt;/p&gt;
&lt;p&gt;• readable code&lt;br&gt;
• predictable behavior&lt;br&gt;
• easy maintenance&lt;/p&gt;
&lt;p&gt;Go enforces a style that makes codebases easier to understand, especially in teams.&lt;/p&gt;
&lt;p&gt;There’s a reason many companies adopt Go for large services: &lt;strong&gt;engineers can read each other’s code easily.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="2-concurrency-that-actually-feels-simple"&gt;2. Concurrency That Actually Feels Simple&lt;/h3&gt;
&lt;p&gt;Modern backend systems are inherently concurrent.&lt;/p&gt;
&lt;p&gt;You’re dealing with:&lt;/p&gt;
&lt;p&gt;• multiple HTTP requests&lt;br&gt;
• database operations&lt;br&gt;
• background jobs&lt;br&gt;
• external services&lt;br&gt;
• queues and event streams&lt;/p&gt;
&lt;p&gt;In many languages, concurrency means dealing with complex thread models, locks, or async frameworks.&lt;/p&gt;
&lt;p&gt;Go solves this elegantly with &lt;strong&gt;goroutines and channels&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Starting a concurrent task is as simple as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go processRequest()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That single keyword launches a lightweight concurrent task.&lt;/p&gt;
&lt;p&gt;Behind the scenes Go handles scheduling, making it incredibly efficient.&lt;/p&gt;
&lt;p&gt;This model makes it easy to build systems like:&lt;/p&gt;
&lt;p&gt;• API servers&lt;br&gt;
• job processors&lt;br&gt;
• streaming services&lt;br&gt;
• high-throughput workers&lt;/p&gt;
&lt;p&gt;without drowning in concurrency complexity.&lt;/p&gt;
&lt;h3 id="3-fast-compilation-changes-your-workflow"&gt;3. Fast Compilation Changes Your Workflow&lt;/h3&gt;
&lt;p&gt;This is something many developers underestimate.&lt;/p&gt;
&lt;p&gt;Go compiles &lt;strong&gt;extremely fast&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That means your development loop becomes:&lt;/p&gt;
&lt;p&gt;edit → build → run → repeat&lt;/p&gt;
&lt;p&gt;And the build step takes seconds.&lt;/p&gt;
&lt;p&gt;Compare that to some compiled languages where builds can take minutes on larger projects.&lt;/p&gt;
&lt;p&gt;Fast compilation encourages experimentation and rapid iteration.&lt;/p&gt;
&lt;p&gt;It also makes Go great for:&lt;/p&gt;
&lt;p&gt;• CLI tools&lt;br&gt;
• microservices&lt;br&gt;
• infrastructure tooling&lt;/p&gt;
&lt;h3 id="4-tooling-that-just-works"&gt;4. Tooling That Just Works&lt;/h3&gt;
&lt;p&gt;One thing Go gets very right is its &lt;strong&gt;tooling philosophy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Many languages rely heavily on external tools and third-party plugins.&lt;/p&gt;
&lt;p&gt;Go ships with most of what you need built in:&lt;/p&gt;
&lt;p&gt;• gofmt for automatic code formatting&lt;br&gt;
• go test for testing&lt;br&gt;
• go build for compiling&lt;br&gt;
• go mod for dependency management&lt;br&gt;
• profiling and benchmarking tools&lt;/p&gt;
&lt;p&gt;Because these tools are standardized, Go projects tend to look and behave consistently.&lt;/p&gt;
&lt;p&gt;You spend less time configuring tools and more time building software.&lt;/p&gt;
&lt;h3 id="5-why-go-is-excellent-for-apis"&gt;5. Why Go Is Excellent for APIs&lt;/h3&gt;
&lt;p&gt;Go has become one of the most popular languages for building APIs, and for good reason.&lt;/p&gt;
&lt;p&gt;It hits the sweet spot between &lt;strong&gt;performance and simplicity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The standard library alone gives you a powerful HTTP server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http.HandleFunc(&amp;#34;/api&amp;#34;, handler)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http.ListenAndServe(&amp;#34;:8080&amp;#34;, nil)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;No heavy frameworks required.&lt;/p&gt;
&lt;p&gt;This makes Go ideal for:&lt;/p&gt;
&lt;p&gt;• REST APIs&lt;br&gt;
• microservices&lt;br&gt;
• internal tooling services&lt;br&gt;
• high-performance backends&lt;/p&gt;
&lt;p&gt;It’s also incredibly easy to deploy because Go compiles to a &lt;strong&gt;single static binary&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;No runtime dependencies.&lt;br&gt;
No complicated deployments.&lt;/p&gt;
&lt;p&gt;Just ship the binary and run it.&lt;/p&gt;
&lt;h3 id="final-thoughts"&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Go isn’t perfect.&lt;/p&gt;
&lt;p&gt;It’s not the most expressive language.&lt;br&gt;
It’s not the best choice for everything.&lt;br&gt;
And sometimes it feels intentionally minimal.&lt;/p&gt;
&lt;p&gt;But for backend systems, that minimalism becomes a strength.&lt;/p&gt;
&lt;p&gt;You get:&lt;/p&gt;
&lt;p&gt;• simple code&lt;br&gt;
• strong performance&lt;br&gt;
• built-in tooling&lt;br&gt;
• great concurrency&lt;/p&gt;
&lt;p&gt;After using more than 10 programming languages, Go remains the language I trust the most when building backend systems.&lt;/p&gt;
&lt;p&gt;Not because it’s flashy.&lt;/p&gt;
&lt;p&gt;Because it &lt;strong&gt;gets the job done reliably.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=9148165fa6dc" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/after-using-10-languages-go-is-still-my-favorite-for-backend-systems-9148165fa6dc?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item><item><title> I Think I Accidentally Built a Full-Stack Framework?</title><link>https://tacherasasi.github.io/posts/i-think-i-accidentally-built-a-full-stack-framework/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/i-think-i-accidentally-built-a-full-stack-framework/</guid><description>&lt;p&gt;&lt;em&gt;Look, I just wanted to share some files.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="the-innocent-beginning"&gt;The Innocent Beginning&lt;/h2&gt;
&lt;p&gt;It started, as these things always do, with a simple problem: I needed to share files between my devices. &amp;ldquo;I&amp;rsquo;ll just write a quick Go server,&amp;rdquo; I said. &amp;ldquo;It&amp;rsquo;ll take an afternoon,&amp;rdquo; I said. &amp;ldquo;It&amp;rsquo;ll be like 200 lines of code,&amp;rdquo; I said.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reader, it was not 200 lines of code.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="what-is-beamdrop-actually"&gt;What Is BeamDrop, Actually?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/ekilie/beamdrop"&gt;BeamDrop&lt;/a&gt; is a self-hosted file sharing server. You run one command, and boom you&amp;rsquo;ve got a beautiful web interface for uploading, downloading, moving, copying, renaming, and searching files. It&amp;rsquo;s like Dropbox, if Dropbox was a single binary and didn&amp;rsquo;t sell your data to train AI models. &lt;em&gt;(No offense, Dropbox. Actually, some offense.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the pitch: &lt;code&gt;beamdrop -dir /path/to/stuff -p mysecretpassword&lt;/code&gt; and you&amp;rsquo;re live. QR code on the terminal so your phone can connect. Password protection. Shareable links with expiry. WebSocket-powered real-time stats. An S3-compatible API so your CI/CD pipeline can talk to it.&lt;/p&gt;
&lt;p&gt;One binary. No Docker required. No Node.js runtime. No &amp;ldquo;please install these 47 dependencies.&amp;rdquo; Just a single executable that runs on macOS, Linux, and Windows.&lt;/p&gt;
&lt;h2 id="how-we-got-here"&gt;How We Got Here&lt;/h2&gt;
&lt;h3 id="stage-1-the-go-server-day-1"&gt;Stage 1: The Go Server (Day 1)&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;I&amp;rsquo;ll embed the frontend right into the Go binary using &lt;code&gt;embed.FS&lt;/code&gt;. Single binary deployment. I&amp;rsquo;m a genius.&amp;rdquo;&lt;/p&gt;
&lt;h3 id="stage-2-the-react-frontend-week-1"&gt;Stage 2: The React Frontend (Week 1)&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;It needs a UI. I&amp;rsquo;ll use React. And Vite. And TypeScript. And Tailwind. And shadcn/ui. For a file server. This is totally proportional.&amp;rdquo;&lt;/p&gt;
&lt;h3 id="stage-3-the-database-week-2"&gt;Stage 3: The Database (Week 2)&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;I need shareable links, so I need a database. SQLite is fine. I&amp;rsquo;ll use GORM. Oh wait, the SQLite driver needs CGO and that breaks cross-compilation. Let me swap to a pure-Go SQLite implementation so the binary stays fully static. This is still a simple file server.&amp;rdquo;&lt;/p&gt;
&lt;h3 id="stage-4-the-s3-api-week-3"&gt;Stage 4: The S3 API (Week 3)&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;What if it also spoke S3? Like, the actual S3 API. With buckets and objects and presigned URLs. For a file server that I built because I wanted to share a PDF with my phone.&amp;rdquo;&lt;/p&gt;
&lt;h3 id="stage-5-the-realization-now"&gt;Stage 5: The Realization (Now)&lt;/h3&gt;
&lt;p&gt;I stared at my codebase. A Go backend with embedded frontend assets. React with client-side routing. SPA fallback handling. SQLite with migrations. WebSocket connections. JWT authentication. API key management. CORS middleware. Security headers. TLS support. Cross-platform builds.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I built a framework.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t mean to. I was just solving problems one at a time. &amp;ldquo;Oh, the frontend routes break on refresh because Go doesn&amp;rsquo;t know about React Router&amp;rdquo; so I added SPA fallback. &amp;ldquo;Oh, &lt;code&gt;CGO_ENABLED=0&lt;/code&gt; breaks SQLite&amp;rdquo; so I swapped to a pure-Go driver. &amp;ldquo;Oh, I need auth&amp;rdquo; JWT and password hashing. Each fix was reasonable in isolation. Together, they formed a full-stack application framework that compiles to a zero-dependency binary.&lt;/p&gt;
&lt;h2 id="the-architecture-that-i-definitely-planned-from-the-start"&gt;The Architecture (That I Definitely Planned From The Start)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;┌──────────────────────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ Single Binary │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ┌────────────────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ React + Vite + TS │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ (embedded via Go) │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └────────┬───────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ┌────────▼───────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ Go HTTP Server │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ┌──────┐ ┌──────────┐ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ SPA │ │ API │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │Fallbk│ │ Routes │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └──────┘ └──────────┘ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ┌──────┐ ┌──────────┐ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ Auth │ │WebSocket │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └──────┘ └──────────┘ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └────────┬───────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ┌────────▼───────────────┐ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ SQLite (pure Go) │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ No CGO required │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └────────────────────────┘ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└──────────────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;&amp;ldquo;Yes, this was the plan all along.&amp;rdquo;&lt;/em&gt; Me, lying.&lt;/p&gt;
&lt;h2 id="things-i-learned-along-the-way"&gt;Things I Learned Along The Way&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. SPA routing in embedded Go servers is a trap.&lt;/strong&gt;
If you serve a React app from Go and someone refreshes on &lt;code&gt;/shares&lt;/code&gt;, Go goes &amp;ldquo;I don&amp;rsquo;t have a file called &lt;code&gt;shares&lt;/code&gt;, here&amp;rsquo;s a 404.&amp;rdquo; The fix: if the requested path has no file extension, serve &lt;code&gt;index.html&lt;/code&gt; and let React Router figure it out. Simple in hindsight. Three hours of debugging in practice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. CGO is the final boss of Go cross-compilation.&lt;/strong&gt;
&lt;code&gt;GOOS=linux GOARCH=arm64 go build&lt;/code&gt; is beautiful until one of your dependencies whispers &amp;ldquo;I need C.&amp;rdquo; Swapping &lt;code&gt;mattn/go-sqlite3&lt;/code&gt; for &lt;code&gt;glebarez/sqlite&lt;/code&gt; (which uses &lt;code&gt;modernc.org/sqlite&lt;/code&gt;, a machine-translated C-to-Go SQLite) was the move. Zero CGO. Builds everywhere. The binary is bigger but my sanity is intact.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;embed.FS&lt;/code&gt; is criminally underrated.&lt;/strong&gt;
Go&amp;rsquo;s &lt;code&gt;//go:embed&lt;/code&gt; directive lets you bake your entire frontend build into the binary. No separate static file serving. No &amp;ldquo;make sure the &lt;code&gt;dist&lt;/code&gt; folder is next to the executable.&amp;rdquo; It just works. Ship one file. Run it. Done.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. You don&amp;rsquo;t need a framework to build a framework.&lt;/strong&gt;
BeamDrop uses no web framework. No Gin, no Echo, no Fiber. Just &lt;code&gt;net/http&lt;/code&gt; and a &lt;code&gt;ServeMux&lt;/code&gt;. The standard library is enough. I&amp;rsquo;ll die on this hill while manually parsing URL paths.&lt;/p&gt;
&lt;h2 id="is-it-actually-a-framework-though"&gt;Is It Actually a Framework Though?&lt;/h2&gt;
&lt;p&gt;Look, if it has:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A backend server with routing and middleware ✅&lt;/li&gt;
&lt;li&gt;An embedded frontend with client-side routing ✅&lt;/li&gt;
&lt;li&gt;A database with ORM and migrations ✅&lt;/li&gt;
&lt;li&gt;Authentication (passwords, JWT, API keys) ✅&lt;/li&gt;
&lt;li&gt;Real-time communication (WebSockets) ✅&lt;/li&gt;
&lt;li&gt;An API layer (REST + S3-compatible) ✅&lt;/li&gt;
&lt;li&gt;Cross-platform single-binary builds ✅&lt;/li&gt;
&lt;li&gt;TLS support ✅&lt;/li&gt;
&lt;li&gt;CORS and security headers ✅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;hellip;then at what point does &amp;ldquo;my file sharing app&amp;rdquo; become &amp;ldquo;a full-stack framework with a file sharing app built on top of it&amp;rdquo;?&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have an answer. I just have a very over-engineered way to send a PDF to my phone.&lt;/p&gt;
&lt;h2 id="try-it"&gt;Try It&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install (macOS Apple Silicon)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -L https://github.com/ekilie/beamdrop/releases/latest/download/beamdrop-darwin-arm64.tar.gz &lt;span class="p"&gt;|&lt;/span&gt; sudo tar -C /usr/local/bin -xzf -
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Run&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;beamdrop -dir ~/Documents -p secretpassword
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it. One binary. Full-stack. No regrets. Okay, some regrets. But the QR code in the terminal is really cool.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;BeamDrop is open source at &lt;a href="https://github.com/ekilie/beamdrop"&gt;github.com/ekilie/beamdrop&lt;/a&gt;. Star it if you&amp;rsquo;ve ever accidentally over-engineered something. So, star it.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Why Wails Wins at IPC for Go Desktop Apps (and How It Stacks Up Against Tauri &amp; Electron)</title><link>https://tacherasasi.github.io/posts/why-wails-wins-at-ipc-for-go-desktop-apps-and-how-it-stacks-up-against-tauri-electron-5a00b202cf09/</link><pubDate>Wed, 27 Aug 2025 00:00:00 +0000</pubDate><guid>https://tacherasasi.github.io/posts/why-wails-wins-at-ipc-for-go-desktop-apps-and-how-it-stacks-up-against-tauri-electron-5a00b202cf09/</guid><description>&lt;p&gt;So you’re building a desktop app with web tech, and you hit the classic problem: &lt;em&gt;how do I get my native backend to talk nicely with my frontend?&lt;/em&gt; That’s where IPC (Inter-Process Communication) comes in the secret sauce that makes your Go (or Rust, or Node) logic play well with your slick React/Vue/HTML UI.&lt;/p&gt;
&lt;p&gt;Here’s my hot take: &lt;strong&gt;Wails does IPC better than anyone else right now.&lt;/strong&gt;&lt;br&gt;
It doesn’t just “let things talk.” It &lt;em&gt;represents&lt;/em&gt; your backend on the frontend, type-safe and all, with almost no effort on your part. Let me break down why that matters, and how it compares to Tauri and Electron.&lt;/p&gt;
&lt;h3 id="wails-ipc-that-feels-native"&gt;Wails: IPC That Feels Native&lt;/h3&gt;
&lt;p&gt;Wails is all about Go on the backend + whatever JS framework you like on the frontend. What makes its IPC stand out is how &lt;em&gt;automatic&lt;/em&gt; it feels. You bind your Go structs or methods, and Wails spits out JS/TS files for you no JSON wrangling, no boilerplate glue code.&lt;/p&gt;
&lt;p&gt;Example:&lt;br&gt;
A Go method like Greet(name string) string instantly becomes a JS import you can call like a normal function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Greet&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;../wailsjs/go/main/App&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Boom. Done. Promise-based, error-handled.&lt;/p&gt;
&lt;p&gt;But the real flex? &lt;strong&gt;Struct mapping.&lt;/strong&gt; If you’ve got a Person struct in Go, Wails creates a Person class in JS, with constructors and helpers. Pass it JSON, pass it objectsWails just translates it all. Add JSON tags to your struct fields, and you’re living the dream.&lt;/p&gt;
&lt;p&gt;And when you update your Go code? A quick wails generate syncs everything. Your frontend always has the latest bindings, complete with TypeScript definitions. It really feels like you’re just calling local JS code, except Go is doing the heavy lifting behind the scenes.&lt;/p&gt;
&lt;h3 id="how-it-compares-tauri-vs-electron"&gt;How It Compares: Tauri vs. Electron&lt;/h3&gt;
&lt;p&gt;Let’s size it up against the two heavyweights.&lt;/p&gt;
&lt;h3 id="tauri-rust"&gt;Tauri (Rust)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Good:&lt;/strong&gt; Lightweight, secure, resource-friendly. Rust fans love it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPC style:&lt;/strong&gt; JS calls Rust functions via invoke(&amp;lsquo;command&amp;rsquo;, { args }). It’s JSON under the hood. Events exist for push messages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Downside:&lt;/strong&gt; Type safety isn’t automatic. You &lt;em&gt;can&lt;/em&gt; use tauri-bindgen for type-safe bindings, but it’s an extra step. Struct mapping is manual unless you go all-in on bindgen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; Fantastic performance, but you’ll spend more time wiring things up.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="electron-node"&gt;Electron (Node)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Good:&lt;/strong&gt; Huge ecosystem, proven in production (Slack, VS Code, etc.).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IPC style:&lt;/strong&gt; Old-school channels with ipcRenderer.send/invoke and webContents.send. It works, but it’s raw.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Downside:&lt;/strong&gt; Zero codegen, zero type safety. You’re writing boilerplate for preload scripts and JSON messages. Structs? Forget it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; If you’re deep in Node land, it’s familiar. But compared to Wails, it feels… clunky.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tldr-comparison"&gt;TL;DR Comparison&lt;/h3&gt;
&lt;p&gt;Feature &lt;strong&gt;Wails (Go)&lt;/strong&gt; &lt;strong&gt;Tauri (Rust)&lt;/strong&gt; &lt;strong&gt;Electron (Node)&lt;/strong&gt; IPC Style Auto JS functions/classes JSON commands + events Manual channels/messages Code Generation Yes, built-in Optional (bindgen) None Type Safety Strong, auto TS Partial, extra setup Manual, if you bother Struct Mapping Automatic (JS classes) Manual / via bindgen Manual JSON Ease of Use High (feels native to JS) Medium (setup-heavy) Low (lots of boilerplate) Best For Go lovers + type safety fans Rust fans + perf seekers Node folks + ecosystem&lt;/p&gt;
&lt;h3 id="so-who-should-use-what"&gt;So, Who Should Use What?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pick Wails&lt;/strong&gt; if you want Go to feel like it lives &lt;em&gt;inside&lt;/em&gt; your frontend. It’s the smoothest, most type-safe IPC experience right now.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pick Tauri&lt;/strong&gt; if performance and security are your top concerns, and you don’t mind extra setup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pick Electron&lt;/strong&gt; if you need Node’s ecosystem or you’re building something huge where library support matters more than IPC elegance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Personally, Wails nails the sweet spot: less boilerplate, more productivity, and it just feels &lt;em&gt;right&lt;/em&gt;. And with Wails v3 on the horizon (better bindings, better tooling), it’s only getting stronger.&lt;/p&gt;
&lt;p&gt;That’s my two cents. What about you? Are you team Wails, Tauri, or Electron? 🚀&lt;/p&gt;
&lt;p&gt;Links if you want to dive deeper: &lt;a href="https://wails.io/"&gt;Wails&lt;/a&gt; | &lt;a href="https://tauri.app/"&gt;Tauri&lt;/a&gt; | &lt;a href="https://www.electronjs.org/"&gt;Electron&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=5a00b202cf09" alt=""&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://medium.com/@tacherasasi/why-wails-wins-at-ipc-for-go-desktop-apps-and-how-it-stacks-up-against-tauri-electron-5a00b202cf09?source=rss-9a41d7ec29fb------2"&gt;Medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;</description></item></channel></rss>