The Appwrite CLI is now written in Go

chiragagg5k1 pts0 comments

The Appwrite CLI is now written in Go - Appwrite

Skip to content<br>Start project

Appwrite helped reduce development time by 60%, and lower server costs<br>by 40%.

Compare Appwrite_ Appwrite vs. Supabase Appwrite vs. Firebase Appwrite vs. Vercel

Docs<br>Pricing<br>Enterprise<br>Customers<br>Blog<br>Changelog

56.9K<br>Go to Console Start project

Blog / The Appwrite CLI is now written in Go

The Appwrite CLI is now written in Go.<br>The command surface does not change. The flags, the appwrite.config.json file, the exit codes, and the --json output stay the same. Only the internals change. A JavaScript bundle of 6.9 MB on Node, with 189 packages below it, becomes one native binary of 14 MB. That binary has no runtime and no dependencies.<br>This post gives the measurements, the reasons for the rewrite, the interfaces that stay the same, and the process that kept the work safe.<br>What changes on the first day<br>Four measurements changed, and you see all four immediately:<br>Start time: 207.6 ms to 11.1 ms. Every command pays this cost.<br>Installation: 330 packages to 2. On disk, that is 209 MB to 13 MB .<br>Memory during push: 283.5 MB to 28.0 MB. The CLI streams the archive instead of holding it in memory.<br>Binary size: 66 MB to 14 MB. The new binary needs no runtime below it.<br>Each measurement comes from the same machine, against the Bun binary that shipped before.<br>The CLI starts in about 10 ms<br>The recording below runs appwrite --help on the two CLIs, 30 runs for each CLI on the same machine. The time decreases from 207.6 ms to 11.1 ms .

Three commands show the same result. The measurements use hyperfine on an Apple M2 Pro.<br>CommandTypeScriptGoSpeed increaseappwrite --version<br>235.0 ms<br>10.4 ms<br>22.5×<br>appwrite --help (full command tree)<br>173.5 ms<br>10.3 ms<br>16.9×<br>appwrite push function --help<br>175.2 ms<br>8.1 ms<br>21.6×

The target was under 10 ms , and one measurement is above it. Root help shows the full command tree in 10.3 ms on macOS. The same command takes 5.6 ms on Linux. Most of the macOS cost comes from process start, not from the CLI:<br>/usr/bin/true takes 2.65 ms on the same machine.<br>An empty Go binary takes 4.35 ms .<br>The installation pulls two packages, not 330<br>The old CLI pulled 330 packages from npm. One of these packages was a native module that needed a code signature. The new CLI pulls two packages: a launcher and one platform binary for your machine.

On disk, the installation decreases from 209 MB to 13 MB .

The new CLI does not use a postinstall script to download a binary. Postinstall scripts break in three common setups:<br>Your CI runner can use --ignore-scripts. npm installs the package, but it does not run the script. The binary is missing, and the CLI does not operate.<br>Your registry can have no internet access. The script cannot reach GitHub to download the binary.<br>npm cannot check what the script downloads. A binary from a script has no checksum in your lockfile.<br>The new CLI ships one package for each platform instead. Each of these packages declares its os and cpu, and npm downloads only the one that matches your machine. That binary arrives as a normal dependency, so npm checks it against your lockfile and keeps it in the cache. No script runs, and no download happens outside npm. The tools esbuild, swc, and turbo install their binaries in the same way.<br>Push uses 28 MB of memory, not 283 MB<br>This measurement is the primary reason for the rewrite. The test pushes a function directory of incompressible data to a live instance, and the peak RSS comes from /usr/bin/time -l. With a 40 MB archive:<br>Peak memory: 283.5 MB to 28.0 MB , which is 10 times less.<br>Wall clock: 18.4 s to 11.0 s , which is 40 percent less.<br>Chunked file uploads are 2.3 times faster.<br>The difference is the upload path. The TypeScript implementation read the full archive into a Buffer before the upload, so its memory increased with the size of your deployment. The Go implementation sends each chunk through an io.SectionReader with an exact Content-Length, so the peak memory is the size of the HTTP write buffer.<br>One binary of 14 MB, with no runtime below it<br>The Bun binary was 66 MB, and one native module in it needed a code signature. The Go binary is near 14 MB. It needs no runtime, and it has no native modules.<br>Why we rewrote the CLI<br>The rewrite started with a measurement, not with an opinion. We first found where the time goes in a usual command. A rewrite is only correct if it removes a cost that you pay each day, and two costs met that condition.<br>The TypeScript CLI had near 27,000 lines of manual code and 24 generated service files. The bundle was 9.5 MB, and it shipped in two forms: a Node bundle on npm, and a Bun binary from the install script. Each command loaded that bundle and registered 606 commands with commander before the first work started.<br>The time in the CLI divides into three groups:<br>The start time, which every command pays. Each command loaded a bundle of 9.5 MB and registered 606 commands before it ran one subcommand. Tab completion was slow for the...

binary appwrite command from time packages

Related Articles