Human Who Codes Newsletter - Node.js, Deno, and Bun


Thoughts on Node.js, Deno, and Bun

For well over a decade, if you wanted to run JavaScript on the server, your only choice was Node.js. It has been battle-hardened by some of the most demanding companies in the world, many of whom paid employees to help maintain or contribute to the project. Then in 2018, Node.js creator Ryan Dahl announced Deno, a new server-side JavaScript runtime that was intended to fix a lot of the problems of Node.js. Fast forward to 2023, and Bun was released as another alternative to Node.js. To decide which one to use, it’s important to understand the approaches that Deno and Bun took to attract developers.

Deno: The anti-Node.js. Deno’s approach was a grand re-thinking of what it meant to be a server-side JavaScript runtime, almost from scratch. Deno’s capability-based security model means that the runtime cannot access outside resources (like files or the internet) by default. You need to tell Deno what a JavaScript program is allowed to do. It also has built-in support for TypeScript, so there’s no need for a separate build step. Deno is written in Rust, so it has memory safety as a foundational piece of the architecture. All of the JavaScript APIs have been redesigned for modern JavaScript using promises and async iterators where appropriate, and by default, Deno includes many Web APIs that developers are familiar with (like fetch()). Initially, Deno couldn’t even use npm packages, as it promoted the ability to use HTTP module imports instead. They would later add support both for Node.js built-in packages and for npm.

Bun: The faster Node.js. Bun’s approach was that there was nothing wrong with Node.js except that it needed to be faster and support TypeScript natively. The main goal, then, was for complete compatibility with existing Node.js applications. You should be able to run “bun install” then “bun start” and have everything work exactly as it would with Node.js. As a result, most of the Node.js built-in packages and npm packages should just work in Bun. While Bun does have some of its own APIs, most notably for file I/O, you mostly don’t need to use them.

With three possible server-side runtimes, what should you do?

It’s important to note a key difference between the three runtimes: Node.js is a community project while Deno and Bun are company projects. While both Deno and Bun are open source, their development happens primarily by paid employees of both companies with the ultimate goal of turning a profit for those companies. I state this as a fact and not as an indictment. Companies need to make money to survive and there’s nothing wrong with doing that by creating open source software. The only problem is what happens to those open source projects if the company fails? Or if the company pivots to something else and abandons the project?

That’s why it makes sense to preserve the ability to switch runtimes easily. It doesn’t matter if you want to use Node.js or Deno or Bun in production right now. What you really want is the ability to change your mind later with as little pain as possible.

What does that mean exactly?

  1. Don’t use Deno- or Bun-specific APIs. Doing so makes it difficult to switch to a different runtime later on. Stick with Node.js APIs that are already supported in Deno and Bun.
  2. Use the “node:” prefix for Node.js internal modules. Both Deno and Bun support a large part of the Node.js internal modules, but they can only identify these modules when the “node:” prefix is present. Node.js works just fine when using the “node:” prefix, so you can ensure compatibility across all three runtimes.
  3. Use npm and JSR packages, not HTTP module specifiers. One of the early innovations in Deno was the ability to specify modules via URLs. Deno is still the only runtime to support this way of loading modules, so it’s best to stick with npm and JSR packages, both of which can be used across all three runtimes.
  4. Use package.json for dependencies. Deno encourages you to use deno.json or jsr.json to specify your dependencies, but it’s just as capable of using package.json.
  5. Always run CI with Node.js, too. Node.js is a great fallback plan if you’re using Deno or Bun due to not being tied to any one company. To ensure your ability to quickly switch runtimes, it pays to always run your continuous integration (CI) tests in Node.js even if you’re deploying to Deno or Bun.

Due to the excellent compatibility of Deno and Bun with Node.js APIs, it’s easier than ever to write code once that will run in any of the three runtimes. The trick really is target Node.js and then use the compatibility features of Deno and Bun. That way, should anytime happen to your chosen runtime, your application will be able to live on without too much trouble.

Key Takeaways

  • Deno and Bun are developed by for-profit companies while Node.js is a community project that is part of a non-profit organization.
  • Node.js compatibility in Deno and Bun is solid, so most Node.js applications will run as-is on Deno and Bun.
  • Even if you decide to deploy with Deno or Bun, target your application at Node.js so it's easy to switch runtimes if necessary.

Understanding JavaScript Promises

I just updated my e-book, Understanding JavaScript Promises, for 2024! It now includes information about Promise.withResolvers() and a whole new chapter on using and creating abortable functions.


Stuff I've Enjoyed this Month

📚 The 6 Types of Working Genius by Patrick M. Lencioni
This book explores the six steps in any creative process and notes that most people are only really good at two of them (their "working geniuses"). To create a high-functioning team, you need people who's working geniuses complement each other. This is an interesting read on how to put together a group of people who can function at their best to achieve a goal.

📚 Slow Productivity by Cal Newport
This book talks about how knowledge workers are unfairly judged based on the appearance of doing work, and how that grew out of earlier manufacturing productivity metrics. Throughout history, some of humankind's greatest accomplishments have happened slowly, over time, at a deliberate pace. The message here is that to avoid burnout, we need to rethink how knowledge work is done and judged.

🎬 What is Retrieval Augmented Generation (RAG)? by IBM Technology
If you've been paying attention to the latest AI developments, you've probably seen the acronym RAG. This short video is a nice overview of what RAG is and why it's important for a lot of AI applications.

💻 PouchDB, the JavaScript database that syncs!
This is a fully client-side database for web applications that can sync to CouchDB when connected to the Internet. PouchDB is local-first, meaning that your application will work completely offline by default. Very interesting project.

📝Add data to a Google Sheet from a Node.js backend by Jacob Paris
One of the first Node.js apps I built was a Contributor License Agreement application that used Google Sheets as the database. This post shows how you can easily write to Google Sheets using the REST API from Node.js.


What I'm Working On

🏠 Real Estate: Nothing of note on my rental properties, but my new deckboard was installed at my place. Unfortunately, after I cleaned it off I discovered several large scratches. The deckboard has been out of stock so I haven't been able to get it fixed yet. Follow my Instagram for real estate photos.

📝 Writing: I wrote an article for LeadDev on using a storytelling framework to present technical information. Whether you're giving a talk or writing an article, this framework helps present your information in a way your audience finds engaging and can relate to.

💻 Open Source: I released ObjectStore, which is an in-memory implementation of a file storage system based on cloud drives like Google Drive and Box. It's mostly useful when you want to stub out a cloud drive API.

💻 ESLint: The post-v9 work continues! I submitted an RFC to alter the way the new config system looks up config files. The intent is to make it easier for monorepos to use ESLint v9. I've also been working on implementing language plugins and actually have a prototype of ESLint linting JSON files. I'm hoping this can be released during July.

Human Who Codes Newsletter

A once-per-month newsletter discussing topics important to senior-level software engineers, with a particular focus on frontend technology and leadership.

Read more from Human Who Codes Newsletter

Thoughts on Weaponizing Open Source When you think of open source software, you might think of it as a gift from someone to the world. They’ve written something of value, and instead of trying to make money off of it, they’ve posted it online for anyone to use (and potentially make money off of) for free. While many projects start that way (Linux, ESLint, etc.), there’s another way open source comes into being: as a weapon against a company’s competition. Android. Perhaps the best example of...

Thoughts on Rates of Change One of the first things I realized about working in codebases is that different parts of the codebase change at different rates. There are some files that are touched very frequently while others can go months, or even years, without being touched. This fascinated me, partly because it reminded me of my closet (why do I even still have those jean shorts?), and partly because people seemed to put such little thought into optimizing their projects to reflect this...

Thoughts on Node.js, Deno, and Bun If you started working in the tech industry after 2005, what’s going on now may seem like a shock. Large tech companies continue to lay people off despite record profits. Smaller tech companies are also laying people off, and in some cases, having “quiet layoffs,” where they find other ways to reduce headcount, including offering employees three months of salary to voluntarily leave the job. As a result, there are more software engineers looking for jobs in...