<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Beamdrop on Tachera W Sasi</title><link>https://tacherasasi.github.io/tags/beamdrop/</link><description>Recent content in Beamdrop on Tachera W Sasi</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Fri, 20 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tacherasasi.github.io/tags/beamdrop/index.xml" rel="self" type="application/rss+xml"/><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></channel></rss>