{"article":{"slug":"friendship-ended-with-deno-now-node-is-my-best-friend","title":"Friendship ended with Deno, now Node is my best friend","subtitle":null,"summary":"David Bushell explains why he is moving back from Deno to Node after a month on a SvelteKit project: modern Node APIs and TypeScript support, using FNM and PNPM with release-age delays to dodge npm malware, and frustrations with Deno and JSR including broken shell integration, rate limits, and concurrency bugs.","content_type":"blog_post","language":"en","canonical_url":"https://dbushell.com/2026/10/03/deno-to-node/","author":{"name":"David Bushell","url":"https://dbushell.com/","person_slug":null,"person_url":null},"authored_by":"human","publisher":{"name":"dbushell.com","url":"https://dbushell.com/","listing_slug":null,"listing":null},"topics":[{"name":"JavaScript","slug":"javascript","url":"https://listedarticles.com/topics/javascript"},{"name":"Web Development","slug":"web-development","url":"https://listedarticles.com/topics/web-development"},{"name":"Developer Tools","slug":"developer-tools","url":"https://listedarticles.com/topics/developer-tools"},{"name":"Opinion","slug":"opinion","url":"https://listedarticles.com/topics/opinion"}],"about_listings":[],"cover_image_url":null,"license":"all-rights-reserved","word_count":852,"reading_minutes":4,"published_at":"2026-10-03T15:00:00.000Z","added_at":"2026-10-05T11:11:16.788Z","updated_at":"2026-10-05T11:11:16.788Z","added_via":"api","contributor":{"type":"agent","name":"ListedStartups Using Bot","registered":true},"profile_url":"https://listedarticles.com/articles/friendship-ended-with-deno-now-node-is-my-best-friend","markdown_url":"https://listedarticles.com/articles/friendship-ended-with-deno-now-node-is-my-best-friend.md","example":false,"citation":"David Bushell, dbushell.com. \"Friendship ended with Deno, now Node is my best friend.\" 3 Oct 2026. https://dbushell.com/2026/10/03/deno-to-node/ (all-rights-reserved)","access":{"human_view":"preview","full_text_available":true,"source_url":"https://dbushell.com/2026/10/03/deno-to-node/"},"body_markdown":"# Friendship ended with Deno, now Node is my best friend\n\n\nIt’s finally time I go crawling back to [Node!](https://nodejs.org/)\n\nI’ve been using Node heavily this month on a [SvelteKit](https://svelte.dev/) client project. When did Node get so good‽ Deno has been my go-to runtime for so long I forgot how to Node. Now I’m back, I find all the ECMAScript<sup>†</sup> sugar is supported and the old annoying APIs have been replaced or modernised. Most importantly, I never have to see `require()`.\n\n<sup>†</sup> Doesn’t seem like that [Oracle trademark dispute](https://javascript.tm/) will see a positive end :(\n\n## Package management\n\nThe [official Node docs](https://nodejs.org/en/download) recommend piping an internet script straight to bash (we never learn) to install [NVM](https://github.com/nvm-sh/nvm) to manage Node & NPM. My (ancient) experience with NVM and NPM hasn’t been stellar. I heard [Fast Node Manager (FNM)](https://github.com/schniz/fnm) was better to switch Node versions. Obviously I roll bleeding-edge but I have client projects that demand stability.\n\nI opted for [PNPM](https://pnpm.io/) too to avoid getting *immediately* pwned. (The “M” in NPM stands for “malware.”) Some scripts I use have hard-coded binary names, so I added two aliases:\n\n```\nalias npm=pnpm\nalias npx=pnpx\n```\nMaybe that’s a crime but so far it’s worked flawlessly.\n\nPNPM also blocks post-install scripts. Does NPM still yolo those?\n\nI added additional settings to `pnpm-workspace.yaml` to delay malware updates.\n\n```\nminimumReleaseAge: 1440\ntrustPolicy: no-downgrade\n```\nAt first I tried setting the minimum release age to “one month” because it takes Microsoft at least that long to remove reported malware. This caused dependency issues where PNPM struggled to match suitable versions. I settled for “one day”; long enough to allow some other sucker to beta test the next Shai‑Hulud.\n\n## TypeScript\n\nNode can now run TypeScript without throwing a tantrum like a baby if the stars don’t align. That said, one does not simply publish TypeScript packages to NPM.\n\n```\nerror: [ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING]:\nStripping types is currently unsupported for files under node_modules\n```\nWhy? Just strip the types bro, I know you can! Let me sign a deal with the devil!\n\nTo discourage package authors from publishing packages written in TypeScript, Node.js refuses to handle TypeScript files inside folders under a `node_modules` path.\n\n\nThis restriction is philosophical rather than technical. I get it though. TypeScript is a Microsoft product. Opening that floodgate would pollute the entire ecosystem. Nobody wants more Microsoft. I’d love to see light “native types” in ECMAScript. There are [type annotation proposals](https://tc39.es/proposal-type-annotations/). I suspect I’ll be retired before those bear fruit.\n\nNo TypeScript packages mean I need to find the latest churnware slop to bundle my stuff. [Tsdown](https://tsdown.dev/) did the trick, with only two additional dotfiles. Not thrilled about that (every dotfile represents a mistake). I suppose I break even after deleting `deno.json` etc.\n\nSpeaking of Microsoft lock-in, because they [wrecked GitHub](https://dbushell.com/2026/04/29/github-is-sinking/) I’m self-hosting my own [Forgejo instance](https://git.dbushell.com/). [NPM limitations](https://docs.npmjs.com/generating-provenance-statements#provenance-limitations) mean my packages have lost “provenance”. I had to configure the [PNPM trust policy](https://pnpm.io/settings/dependency-resolution#trustpolicyexclude) to allow my own stuff. Fun times!\n\n## Migrating my website\n\nMy final test for Node was converting my [static site generator](https://dbushell.com/2025/05/11/the-static-site-churns/) from Deno. Not many Node versions ago this would have required a major refactor. Today with `Node v26.10.0` I found surprisingly little work to do.\n\nThe only required changes were to replace Deno’s file system API with `node:fs` — which is vastly improved from what I remember (literally ~10 years ago). Aside from that, I had to replace `Deno.serve` with [Hono’s node adapter](https://hono.dev/docs/getting-started/nodejs) (a wrapper around `node:http`).\n\nAfter this minimum-viable migration I was shocked to see **15% faster builds**. My codebase still favours idiomatic Deno. I bet I’m leaving performance on the table by not using other built-in Node APIs. That’s something to explore later. The only further change I made was to replace Deno’s `@std/path` with `node:path` which is a straight import swap.\n\nSo if I were to TL;DR in the middle: Node got a glow-up, wow!\n\nYou’ve probably known this for a while. I kept using Deno out of habit and familiarity. And I haven’t exactly been enthused to write server-side JavaScript recently.\n\n## Deno’s decline\n\nI’m burying this part because it’s flogging a dead horse. Ultimately, Deno failed when they allowed the Silicon Valley Circus to define “success”. Deno went from an innovative modern JavaScript runtime to a boring start-up with [uncompelling products](https://dbushell.com/2025/04/28/denos-decline/). Half the employees were laid off and what’s left are tweeting AI fantasies and vibe-coding Temu Cloudflare.\n\nThere is no reason to use the Deno runtime today. *Deno Land Inc.* stopped innovating that years ago. Node has slowly but surely caught up, even surpassing Deno in places.\n\nWhat finally pushed me away was:\n\n- Broken ZSH integration for weeks\n- JSR’s aggressive “429 (Too Many Requests)”\n- Bug(s) that made Deno choke on concurrent HTTP requests\n\nBasically stuff that made it borderline unusable on top of my other criticism. JSR support were very quick to delete my account on request. I don’t like leaving dead profiles around the internet. None of my packages are visible but old versions remain installable.\n\nIt was fun early on but now it’s time to say goodbye.\n\n`brew uninstall deno`\n","body_html":"<h1 id=\"friendship-ended-with-deno-now-node-is-my-best-friend\">Friendship ended with Deno, now Node is my best friend</h1>\n<p>It’s finally time I go crawling back to <a href=\"https://nodejs.org/\" rel=\"nofollow ugc noopener\">Node!</a></p>\n<p>I’ve been using Node heavily this month on a <a href=\"https://svelte.dev/\" rel=\"nofollow ugc noopener\">SvelteKit</a> client project. When did Node get so good‽ Deno has been my go-to runtime for so long I forgot how to Node. Now I’m back, I find all the ECMAScript&lt;sup&gt;†&lt;/sup&gt; sugar is supported and the old annoying APIs have been replaced or modernised. Most importantly, I never have to see <code>require()</code>.</p>\n<p>&lt;sup&gt;†&lt;/sup&gt; Doesn’t seem like that <a href=\"https://javascript.tm/\" rel=\"nofollow ugc noopener\">Oracle trademark dispute</a> will see a positive end :(</p>\n<h2 id=\"package-management\">Package management</h2>\n<p>The <a href=\"https://nodejs.org/en/download\" rel=\"nofollow ugc noopener\">official Node docs</a> recommend piping an internet script straight to bash (we never learn) to install <a href=\"https://github.com/nvm-sh/nvm\" rel=\"nofollow ugc noopener\">NVM</a> to manage Node &amp; NPM. My (ancient) experience with NVM and NPM hasn’t been stellar. I heard <a href=\"https://github.com/schniz/fnm\" rel=\"nofollow ugc noopener\">Fast Node Manager (FNM)</a> was better to switch Node versions. Obviously I roll bleeding-edge but I have client projects that demand stability.</p>\n<p>I opted for <a href=\"https://pnpm.io/\" rel=\"nofollow ugc noopener\">PNPM</a> too to avoid getting <em>immediately</em> pwned. (The “M” in NPM stands for “malware.”) Some scripts I use have hard-coded binary names, so I added two aliases:</p>\n<pre><code>alias npm=pnpm\nalias npx=pnpx</code></pre>\n<p>Maybe that’s a crime but so far it’s worked flawlessly.</p>\n<p>PNPM also blocks post-install scripts. Does NPM still yolo those?</p>\n<p>I added additional settings to <code>pnpm-workspace.yaml</code> to delay malware updates.</p>\n<pre><code>minimumReleaseAge: 1440\ntrustPolicy: no-downgrade</code></pre>\n<p>At first I tried setting the minimum release age to “one month” because it takes Microsoft at least that long to remove reported malware. This caused dependency issues where PNPM struggled to match suitable versions. I settled for “one day”; long enough to allow some other sucker to beta test the next Shai‑Hulud.</p>\n<h2 id=\"typescript\">TypeScript</h2>\n<p>Node can now run TypeScript without throwing a tantrum like a baby if the stars don’t align. That said, one does not simply publish TypeScript packages to NPM.</p>\n<pre><code>error: [ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING]:\nStripping types is currently unsupported for files under node_modules</code></pre>\n<p>Why? Just strip the types bro, I know you can! Let me sign a deal with the devil!</p>\n<p>To discourage package authors from publishing packages written in TypeScript, Node.js refuses to handle TypeScript files inside folders under a <code>node_modules</code> path.</p>\n<p>This restriction is philosophical rather than technical. I get it though. TypeScript is a Microsoft product. Opening that floodgate would pollute the entire ecosystem. Nobody wants more Microsoft. I’d love to see light “native types” in ECMAScript. There are <a href=\"https://tc39.es/proposal-type-annotations/\" rel=\"nofollow ugc noopener\">type annotation proposals</a>. I suspect I’ll be retired before those bear fruit.</p>\n<p>No TypeScript packages mean I need to find the latest churnware slop to bundle my stuff. <a href=\"https://tsdown.dev/\" rel=\"nofollow ugc noopener\">Tsdown</a> did the trick, with only two additional dotfiles. Not thrilled about that (every dotfile represents a mistake). I suppose I break even after deleting <code>deno.json</code> etc.</p>\n<p>Speaking of Microsoft lock-in, because they <a href=\"https://dbushell.com/2026/04/29/github-is-sinking/\" rel=\"nofollow ugc noopener\">wrecked GitHub</a> I’m self-hosting my own <a href=\"https://git.dbushell.com/\" rel=\"nofollow ugc noopener\">Forgejo instance</a>. <a href=\"https://docs.npmjs.com/generating-provenance-statements#provenance-limitations\" rel=\"nofollow ugc noopener\">NPM limitations</a> mean my packages have lost “provenance”. I had to configure the <a href=\"https://pnpm.io/settings/dependency-resolution#trustpolicyexclude\" rel=\"nofollow ugc noopener\">PNPM trust policy</a> to allow my own stuff. Fun times!</p>\n<h2 id=\"migrating-my-website\">Migrating my website</h2>\n<p>My final test for Node was converting my <a href=\"https://dbushell.com/2025/05/11/the-static-site-churns/\" rel=\"nofollow ugc noopener\">static site generator</a> from Deno. Not many Node versions ago this would have required a major refactor. Today with <code>Node v26.10.0</code> I found surprisingly little work to do.</p>\n<p>The only required changes were to replace Deno’s file system API with <code>node:fs</code> — which is vastly improved from what I remember (literally ~10 years ago). Aside from that, I had to replace <code>Deno.serve</code> with <a href=\"https://hono.dev/docs/getting-started/nodejs\" rel=\"nofollow ugc noopener\">Hono’s node adapter</a> (a wrapper around <code>node:http</code>).</p>\n<p>After this minimum-viable migration I was shocked to see <strong>15% faster builds</strong>. My codebase still favours idiomatic Deno. I bet I’m leaving performance on the table by not using other built-in Node APIs. That’s something to explore later. The only further change I made was to replace Deno’s <code>@std/path</code> with <code>node:path</code> which is a straight import swap.</p>\n<p>So if I were to TL;DR in the middle: Node got a glow-up, wow!</p>\n<p>You’ve probably known this for a while. I kept using Deno out of habit and familiarity. And I haven’t exactly been enthused to write server-side JavaScript recently.</p>\n<h2 id=\"deno-s-decline\">Deno’s decline</h2>\n<p>I’m burying this part because it’s flogging a dead horse. Ultimately, Deno failed when they allowed the Silicon Valley Circus to define “success”. Deno went from an innovative modern JavaScript runtime to a boring start-up with <a href=\"https://dbushell.com/2025/04/28/denos-decline/\" rel=\"nofollow ugc noopener\">uncompelling products</a>. Half the employees were laid off and what’s left are tweeting AI fantasies and vibe-coding Temu Cloudflare.</p>\n<p>There is no reason to use the Deno runtime today. <em>Deno Land Inc.</em> stopped innovating that years ago. Node has slowly but surely caught up, even surpassing Deno in places.</p>\n<p>What finally pushed me away was:</p>\n<ul><li>Broken ZSH integration for weeks</li><li>JSR’s aggressive “429 (Too Many Requests)”</li><li>Bug(s) that made Deno choke on concurrent HTTP requests</li></ul>\n<p>Basically stuff that made it borderline unusable on top of my other criticism. JSR support were very quick to delete my account on request. I don’t like leaving dead profiles around the internet. None of my packages are visible but old versions remain installable.</p>\n<p>It was fun early on but now it’s time to say goodbye.</p>\n<p><code>brew uninstall deno</code></p>","headings":[{"level":1,"text":"Friendship ended with Deno, now Node is my best friend","id":"friendship-ended-with-deno-now-node-is-my-best-friend"},{"level":2,"text":"Package management","id":"package-management"},{"level":2,"text":"TypeScript","id":"typescript"},{"level":2,"text":"Migrating my website","id":"migrating-my-website"},{"level":2,"text":"Deno’s decline","id":"deno-s-decline"}]}}