{"id":159,"date":"2026-04-27T13:03:34","date_gmt":"2026-04-27T13:03:34","guid":{"rendered":"https:\/\/abrarqasim.com\/blog\/bun-vs-node-2026-what-i-actually-run-in-production\/"},"modified":"2026-04-27T13:03:34","modified_gmt":"2026-04-27T13:03:34","slug":"bun-vs-node-2026-what-i-actually-run-in-production","status":"publish","type":"post","link":"https:\/\/abrarqasim.com\/blog\/bun-vs-node-2026-what-i-actually-run-in-production\/","title":{"rendered":"Bun vs Node in 2026: What I Actually Run in Production"},"content":{"rendered":"<p>I had a side project last winter that was killing me with cold starts. Tiny Node service, deployed to a small VPS, ran in a Docker container, and every restart took about 3.8 seconds before it was answering requests. That&rsquo;s nothing on a real server, but it was annoying for an internal tool I bounce in and out of all day. So one Friday I spent an evening porting it to Bun. The cold start dropped to 0.4 seconds.<\/p>\n<p>I&rsquo;d been hearing the Bun hype for two years and ignoring it. Now I had to take it seriously.<\/p>\n<p>This post is where I&rsquo;ve landed after about six months of mixing Bun and Node in real work. Short version: Bun has earned a spot in my toolbox, but it hasn&rsquo;t replaced Node for the things I most care about being boring.<\/p>\n<h2 id=\"where-bun-actually-shines-for-me\">Where Bun actually shines for me<\/h2>\n<p>Three places, all of them about speed of feedback rather than raw runtime throughput.<\/p>\n<p>The first is package install. <code>bun install<\/code> finishes in seconds for projects where <code>npm install<\/code> makes me get coffee. On a fresh clone of a mid-size Next.js app I have, npm took 47 seconds, pnpm took 12, and bun took 4. The numbers are not the headline. The headline is that the wait is short enough that I stop context-switching, which means I keep working.<\/p>\n<p>The second is running TypeScript directly. Bun handles <code>bun run src\/index.ts<\/code> with no extra config, no <code>tsx<\/code>, no <code>ts-node<\/code>, no transpile step. Here&rsquo;s the before-and-after for a one-off script:<\/p>\n<pre><code class=\"language-bash\"># The old Node way\nnpm i -D tsx typescript @types\/node\nnpx tsx .\/script.ts\n\n# Bun\nbun .\/script.ts\n<\/code><\/pre>\n<p>Node has been catching up here with <code>--experimental-strip-types<\/code>, but Bun made this a non-event years earlier and the experience is still smoother for the small stuff.<\/p>\n<p>The third is bundled testing. <code>bun test<\/code> gives you a Jest-flavored runner without installing Jest:<\/p>\n<pre><code class=\"language-ts\">\/\/ math.test.ts\nimport { expect, test } from &quot;bun:test&quot;;\n\ntest(&quot;adds&quot;, () =&gt; {\n  expect(2 + 2).toBe(4);\n});\n<\/code><\/pre>\n<pre><code class=\"language-bash\">bun test\n# bun test v1.2.x\n# math.test.ts:\n# (pass) adds [0.32ms]\n<\/code><\/pre>\n<p>For tiny libraries this is wonderful. No jest config, no ts-jest, no <code>@types\/jest<\/code>.<\/p>\n<h2 id=\"where-node-still-wins-and-why-i-keep-it-on-the-production-server\">Where Node still wins, and why I keep it on the production server<\/h2>\n<p>A few honest reasons.<\/p>\n<p>Compatibility. The npm ecosystem assumes Node. Most of the time Bun&rsquo;s compatibility layer is good enough that you won&rsquo;t notice. Some of the time it isn&rsquo;t. I hit a real one with a Postgres driver that used a Node-specific socket option, and another with a Sentry middleware that depended on <code>process.binding<\/code>. Both got fixed eventually. Both wasted an afternoon. On a production service for a paying client I don&rsquo;t want to debug &ldquo;is this a Bun bug or my bug?&rdquo; at 11pm.<\/p>\n<p>Operational maturity. Node has fifteen years of war stories. Heap snapshots, V8 flame graphs, every PaaS supports it, AWS Lambda has a maintained runtime, Datadog has a tracer that just works, k8s autoscaling samples are all written for Node. Bun has growing support, but you&rsquo;ll occasionally find yourself opening GitHub issues that nobody has ever opened before. That&rsquo;s fun on a side project. It&rsquo;s not fun for the on-call rotation.<\/p>\n<p>Long-term API stability. Node moves slowly on purpose. Bun moves fast and breaks small things between point releases. I&rsquo;ve had dependencies stop working after a <code>bun upgrade<\/code> that should have been a no-op. For something I want to redeploy in two years without touching, Node is the safer bet.<\/p>\n<p>If you read those reasons and felt yourself nodding, you understand why I describe the split as: Bun for my dev loop, Node for the stuff that pays me.<\/p>\n<h2 id=\"deno-is-the-one-i-keep-almost-using\">Deno is the one I keep almost using<\/h2>\n<p>I want to like Deno. The security model is genuinely the right design. Permissions per script (<code>--allow-net<\/code>, <code>--allow-read=\/etc<\/code>) is what every runtime should do.<\/p>\n<p>The blocker for me has been npm interop friction. Deno added npm support in 1.28 and refined it across the 2.x line, but installing certain packages still produces strange errors with peer deps that resolve fine in Node and Bun. Last month I tried to port a small Hono app to Deno and stopped after the third &ldquo;specifier not found&rdquo; warning that I couldn&rsquo;t quickly explain.<\/p>\n<p>Deno 2.x has narrowed the gap a lot, and if I were starting a brand new app with no legacy npm deps, I&rsquo;d seriously consider it. For now it&rsquo;s the runtime I keep almost using and not quite picking. If you&rsquo;re curious, the <a href=\"https:\/\/deno.com\/blog\/v2.0\" rel=\"nofollow noopener\" target=\"_blank\">Deno 2 announcement post<\/a> is worth a read for the npm and Node compatibility story.<\/p>\n<h2 id=\"a-small-honest-benchmark\">A small, honest benchmark<\/h2>\n<p>Caveat: a tiny benchmark from one app. Take it as a flavor, not a verdict.<\/p>\n<p>I ran a small Hono server doing two things: serving a static JSON config, and doing a Postgres query through a connection pool. Both runtimes used the same Hono version and the same <code>pg<\/code> driver. The host was a 2-vCPU Hetzner instance, hot pool, 30-second wrk run.<\/p>\n<pre><code class=\"language-text\"># Static JSON route, 200 connections, 30s\nNode 22.11:  41,200 req\/s, p99 11ms\nBun 1.2.4:   58,900 req\/s, p99 8ms\n\n# Postgres query route, 50 connections, 30s\nNode 22.11:  6,800 req\/s, p99 38ms\nBun 1.2.4:   7,300 req\/s, p99 35ms\n<\/code><\/pre>\n<p>Two reactions. First, on the IO-bound route the gap nearly vanishes, because both runtimes are waiting on Postgres. Most real apps live in that second case. Second, the synthetic JSON test is real but rarely the bottleneck for anything I ship. Don&rsquo;t migrate a service for these numbers alone.<\/p>\n<h2 id=\"what-i-run-for-what\">What I run for what<\/h2>\n<p>My current split, today:<\/p>\n<ul>\n<li><strong>CLIs and scripts<\/strong>: Bun. The TypeScript-without-config plus fast install make this a one-line decision.<\/li>\n<li><strong>Local dev for Next.js apps<\/strong>: Bun for <code>bun install<\/code>, then <code>next dev<\/code> itself runs on Node because that&rsquo;s what Next.js officially supports. Mixing them doesn&rsquo;t break anything in practice.<\/li>\n<li><strong>Backend services in production<\/strong>: Node, deployed via Docker. I&rsquo;ll revisit in a year.<\/li>\n<li><strong>One-off endpoints I host on Cloudflare Workers<\/strong>: neither, because Workers has its own runtime. Different conversation.<\/li>\n<li><strong>Edge functions in Vercel<\/strong>: V8 isolate runtime. Also a different conversation.<\/li>\n<\/ul>\n<p>If you want a deeper dive on the Next.js side of this, I wrote about how I <a href=\"https:\/\/abrarqasim.com\/blog\/nextjs-app-router-vs-pages-router-practical-guide\/\" rel=\"noopener\">chose between the App Router and Pages Router<\/a> earlier this year. Bun vs Node is a smaller decision than that one.<\/p>\n<h2 id=\"whats-actually-changed-for-me\">What&rsquo;s actually changed for me<\/h2>\n<p>Six months ago I&rsquo;d have called Bun a curiosity. Today, my dotfiles install Bun on every new machine, and I reach for it any time I&rsquo;m starting fresh. What hasn&rsquo;t changed is that I still write production services in Node, and I&rsquo;d encourage anyone running a small business on top of a Node app to do the same for now.<\/p>\n<p>The Bun team has shipped a lot. Their <a href=\"https:\/\/bun.sh\/blog\/bun-v1.2\" rel=\"nofollow noopener\" target=\"_blank\">v1.2 announcement<\/a> is a good read if you want a sense of the pace. The <a href=\"https:\/\/nodejs.org\/en\/blog\/announcements\/v22-release-announce\" rel=\"nofollow noopener\" target=\"_blank\">Node.js 22 release notes<\/a> are quieter, which I think reflects the right priorities for that project.<\/p>\n<p>If you do consulting work where stability matters as much as features, you may end up making the same split I did. I write more about that kind of trade-off on <a href=\"https:\/\/abrarqasim.com\/work\" rel=\"noopener\">my work page<\/a> if it&rsquo;s the sort of thing you care about.<\/p>\n<h2 id=\"one-thing-to-try-this-week\">One thing to try this week<\/h2>\n<p>Pick one CLI script you&rsquo;ve been meaning to write. A small thing that hits an API and writes a CSV, say. Write it as a single <code>script.ts<\/code> file. Run it with <code>bun .\/script.ts<\/code>. Notice how much friction it removed. Then decide for yourself whether to push further.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>My honest take on Bun vs Node after six months of mixing both: where Bun earned a real spot in my workflow, and where Node still wins for production.<\/p>\n","protected":false},"author":2,"featured_media":158,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"","rank_math_description":"My honest take on Bun vs Node after six months of mixing both: where Bun earned a real spot in my workflow, and where Node still wins for production.","rank_math_focus_keyword":"bun vs node","rank_math_canonical_url":"","rank_math_robots":"","footnotes":""},"categories":[147,165],"tags":[166,169,44,167,19,168],"class_list":["post-159","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-backend","category-javascript","tag-bun","tag-deno","tag-javascript","tag-nodejs","tag-performance","tag-runtime"],"_links":{"self":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/159","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/comments?post=159"}],"version-history":[{"count":0,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/posts\/159\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media\/158"}],"wp:attachment":[{"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/media?parent=159"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/categories?post=159"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/abrarqasim.com\/blog\/wp-json\/wp\/v2\/tags?post=159"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}