Skip to main content
  1. Blog/

Node.js in 2021 — A Year of Quiet Maturation

·979 words·5 mins
Osmond van Hemert
Author
Osmond van Hemert
JavaScript & Node.js - This article is part of a series.
Part : This Article

As 2021 wraps up, I want to reflect on a platform that’s been central to my work for years: Node.js. This wasn’t a year of dramatic headlines for Node — no major controversies, no existential threats. Instead, it was a year of steady, purposeful maturation. And honestly, for a technology platform that powers millions of production applications, boring progress is exactly what you want.

Node 16 Goes LTS
#

The headline event was Node.js 16 entering Long Term Support in October, becoming the recommended version for production use. Node 16 brought several features that have been cooking for a while:

V8 9.4 under the hood means better performance and newer JavaScript features. Array.prototype.at(), Object.hasOwn(), and RegExp match indices are small quality-of-life improvements, but they add up.

Stable Timers Promises API — you can now import { setTimeout } from 'timers/promises' and await setTimeout(1000) instead of wrapping callbacks in Promises. It’s a tiny thing, but I use timers constantly in test code and integration scripts, and this reads so much better.

npm 8 ships with Node 16, bringing workspaces that are actually usable. If you’re managing a monorepo, npm workspaces are now a viable alternative to Yarn workspaces or Lerna for many use cases.

Meanwhile, Node 17 came out in October as the Current release with some interesting experimental features. The one I’m watching most closely is the built-in test runner.

The Built-in Test Runner
#

Node 17 shipped with an experimental node:test module, and while it’s not production-ready yet, it signals an important direction. For years, testing in Node has required a third-party framework — Mocha, Jest, Ava, tap. Each has its own quirks, configuration, and ecosystem.

Having a built-in test runner means that simple projects don’t need a testing dependency at all. The API is clean:

import test from 'node:test';
import assert from 'node:assert';

test('basic arithmetic', (t) => {
  assert.strictEqual(1 + 1, 2);
});

test('async operation', async (t) => {
  const result = await fetchData();
  assert.ok(result.length > 0);
});

Will this replace Jest for complex applications? Probably not anytime soon — Jest’s mocking system, snapshot testing, and rich ecosystem are hard to replicate. But for libraries, small services, and quick scripts, having a zero-dependency test option is valuable. I’ve already started using it for utility libraries where adding Jest felt like overkill.

The TypeScript Migration Continues
#

One of the most notable trends in the Node.js ecosystem this year has been the continued migration toward TypeScript. The State of JS 2021 survey data isn’t fully in yet, but from what I’ve seen in the ecosystem, TypeScript adoption in Node.js projects has crossed a tipping point.

Major frameworks have gone TypeScript-first: NestJS continues to gain traction as the “enterprise” Node.js framework, and tRPC emerged this year as an interesting approach to end-to-end type-safe APIs. Even Express, the eternal incumbent, sees most new tutorial content written in TypeScript.

What’s interesting is watching how this shifts the Node.js development experience. TypeScript adds a compilation step, which traditionally goes against Node’s “edit and run” philosophy. Tools like tsx and ts-node smooth over this friction, but there’s still an ongoing tension between TypeScript’s safety benefits and the added complexity.

In my own projects, I’ve fully committed to TypeScript for anything beyond quick scripts. The productivity gain from catching type errors at compile time — especially in large codebases with multiple contributors — far outweighs the setup overhead.

ESM: Almost There, Not Quite
#

ECMAScript Modules (ESM) support in Node.js continued its slow march toward becoming the default in 2021. Node 16 improved ESM support, and more packages are shipping ESM builds alongside CommonJS. But the migration is messy.

The fundamental tension is that CommonJS and ESM have different semantics — CJS is synchronous and uses require(), ESM is asynchronous and uses import. You can import CJS from ESM, but you can’t easily require ESM from CJS. This means the transition has to happen leaf-to-leaf through the dependency tree, and any package that goes ESM-only risks breaking downstream consumers that haven’t migrated yet.

Some prominent packages made the leap this year — notably the got HTTP client and several packages in Sindre Sorhus’s ecosystem went ESM-only. This caused genuine pain for many developers and sparked heated debate about whether going ESM-only is responsible or reckless.

My take: ESM is the future, and the migration is worth it. But going ESM-only in a library right now is premature for most packages. Dual publishing (CJS + ESM) is more work but shows respect for your downstream consumers. We’ll get there, but forcing the transition creates unnecessary friction.

Deno and the Competition
#

I’d be remiss not to mention Deno, Ryan Dahl’s successor project to Node.js. Deno had a productive 2021 — the Deno Deploy edge computing platform launched, and the runtime continued to improve. But in terms of production adoption, Deno remains a niche player.

That said, Deno is influencing Node.js in positive ways. Node’s built-in test runner, improved permission considerations, and the push toward web-standard APIs are all partially inspired by Deno’s design decisions. Competition in the JavaScript runtime space is healthy.

My Take
#

Node.js in 2021 reminds me of where Java was about a decade ago — mature, widely deployed, and evolving steadily rather than dramatically. That’s not a bad thing. When your platform runs a significant chunk of the internet’s backend services, stability and incremental improvement are features, not bugs.

For 2022, I’m most interested in three things: the built-in test runner reaching stability, further ESM adoption reducing the dual-module headaches, and whether the performance work in Node 18 narrows the gap with alternatives like Deno and Bun (the new Zig-based JavaScript runtime that’s been generating buzz).

Node.js isn’t the exciting new thing anymore. It’s the reliable thing. After thirty years in this industry, I’ve learned to value reliable over exciting every single time.

Here’s to another year of boring, productive progress.

JavaScript & Node.js - This article is part of a series.
Part : This Article