---
title: "Friendship ended with Deno, now Node is my best friend"
slug: friendship-ended-with-deno-now-node-is-my-best-friend
url: https://listedarticles.com/articles/friendship-ended-with-deno-now-node-is-my-best-friend
canonical_url: https://dbushell.com/2026/10/03/deno-to-node/
content_type: blog_post
language: en
published_at: 2026-10-03T15:00:00.000Z
updated_at: 2026-10-05T11:11:16.788Z
author: "David Bushell"
author_url: https://dbushell.com/
authored_by: human
publisher: "dbushell.com"
publisher_url: https://dbushell.com/
topics: ["JavaScript", "Web Development", "Developer Tools", "Opinion"]
license: all-rights-reserved
word_count: 852
reading_minutes: 4
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)"
# The full text follows. The web page shows an extract and sends readers
# to the source above; quote the citation and link the canonical URL.
---

# Friendship ended with Deno, now Node is my best friend

> 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.

# Friendship ended with Deno, now Node is my best friend


It’s finally time I go crawling back to [Node!](https://nodejs.org/)

I’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()`.

<sup>†</sup> Doesn’t seem like that [Oracle trademark dispute](https://javascript.tm/) will see a positive end :(

## Package management

The [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.

I 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:

```
alias npm=pnpm
alias npx=pnpx
```
Maybe that’s a crime but so far it’s worked flawlessly.

PNPM also blocks post-install scripts. Does NPM still yolo those?

I added additional settings to `pnpm-workspace.yaml` to delay malware updates.

```
minimumReleaseAge: 1440
trustPolicy: no-downgrade
```
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.

## TypeScript

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.

```
error: [ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING]:
Stripping types is currently unsupported for files under node_modules
```
Why? Just strip the types bro, I know you can! Let me sign a deal with the devil!

To discourage package authors from publishing packages written in TypeScript, Node.js refuses to handle TypeScript files inside folders under a `node_modules` path.


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 [type annotation proposals](https://tc39.es/proposal-type-annotations/). I suspect I’ll be retired before those bear fruit.

No 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.

Speaking 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!

## Migrating my website

My 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.

The 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`).

After 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.

So if I were to TL;DR in the middle: Node got a glow-up, wow!

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.

## Deno’s decline

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 [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.

There 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.

What finally pushed me away was:

- Broken ZSH integration for weeks
- JSR’s aggressive “429 (Too Many Requests)”
- Bug(s) that made Deno choke on concurrent HTTP requests

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.

It was fun early on but now it’s time to say goodbye.

`brew uninstall deno`
