For 15 years, Node.js has been a cornerstone of web development. Since its release in 2009, it's come a long way from a simple niche technology, now powering over 6.3 million websites, countless APIs, and used by 98% of the Fortune 500.
As a powerful open-source runtime environment, Node is perfectly suited for the challenges of digital transformation. Built on the familiar foundation of JavaScript, Node.js boasts a lightweight and event-driven architecture. This makes it ideal for building scalable real-time applications that can handle high volumes of concurrent requests – a critical requirement for today's API-driven world.
This, coupled with its active and ever-growing open-source community and the strong support from the OpenJS foundation, has made it a pillar of contemporary web development.
But recently, rumors of Node.js' decline have begun to circulate. Is there any truth to these claims?
In this blog, we'll dive into key metrics that paint a different picture – a picture of a thriving Node.js ecosystem with a bright future. We will also take a look at major features that have been shipped and will soon be live on Node.js.
Technology is a Never-Ending Loop
Some might argue that new technologies inevitably render older ones obsolete. But the truth is, advancements often build upon existing foundations. Take COBOL, for example. This programming language, created in 1959, is still actively used today. While it may not be the go-to choice for cutting-edge web development, COBOL remains crucial for maintaining core business systems in banking, finance, and government agencies. According to the latest Tiobe index, COBOL is on the rise, currently sitting between Ruby and Rust in terms of popularity. Its enduring relevance highlights a key point: technological progress doesn't always mean discarding the past.
COBOL is on the rise (Source: tiobe.com/tiobe-index)
Let’s consider another established player in the web development landscape: jQuery. This JavaScript library, launched three years before Node.js, boasts impressive usage statistics – over 95% of websites with JavaScript and 77% of all websites in general. jQuery's enduring popularity demonstrates that a technology's age doesn't necessarily dictate its relevance. Just like jQuery, Node.js, despite being younger, has the potential to maintain its position as a valuable tool for web developers.
jQuery is used by 94.4% of the JS-enabled websites - (Source: w3techs.com/technologies/overview/javascrip..)
Node.js’ Current Momentum
According to a StackOverflow survey, Nodejs is the most popular technology. This success hinges on the powerful pairing of Node.js and the npm registry. This innovative duo solved the challenge of software reuse at a massive scale, something previously out of reach.
Source: StackOverflow
As a result, the use of pre-written code modules has exploded, solidifying Node.js' position as a development powerhouse.
The Readable-stream downloads grew from just above 3 billion in 2022 to close to 7 billion in 2023, which means usage doubled within three years.
Total Node.js downloads:
Node.js sees a whopping 130 million downloads every month.
However, it's important to understand what this number includes. A significant portion of these downloads are actually headers. These headers are temporary files downloaded during the npm i command to compile binary add-ons. Once compiled, the add-ons are stored on your system for later use.
Source: nodedownloads.nodeland.dev
Looking at the downloads by operating system, Linux is at the top of the leaderboard. This makes sense because Linux is often the preferred choice for continuous integration (CI) – the automated testing process software goes through during development. While Linux dominates CI, open-source projects (OSS) often perform additional tests on Windows for good measure.
This trend of high downloads translates to real usage. In 2021, there were 30 million downloads of Node.js binaries, and that number jumped to 50 million in 2024. In 2023, the Node.js image on the Docker hub received over 800 million downloads, providing valuable insight into how much Node.js is used in production.
Keeping Your Apps Secure: Update Your Node.js Version
Many developers and teams are unintentionally putting their applications at risk by not updating Node.js. Here's why staying current is crucial.
Node.js offers a Long-Term Support (LTS) schedule to ensure stability and security for critical applications. However, versions eventually reach their end-of-life, meaning they no longer receive security patches. This leaves applications built with these outdated versions vulnerable to attacks.
For example, Node.js versions 14 and 16 are now deprecated. Despite this, millions of downloads for these versions still occur each month– Node 16 was downloaded 25 million times in February, while Node 14 was around 10 million times. Shockingly, some developers are even using much older versions like Node 10 and 12.
LTS Schedule
Here's the good news: updating Node.js is easy. The recommended approach is to upgrade every two LTS releases. For instance, if you're currently using Node.js 16 (which is no longer supported), you should migrate to the latest LTS version, which is currently Node.js 20. Don't let outdated software expose your applications to security threats.
Node works hard to keep you safe
Node.js takes security seriously. Security submissions undergo a thorough assessment by the Node Technical Steering Committee (TSC) to determine their validity. This team works hard to ensure a fast response time, aiming for an initial response within 5 days and typically achieving it within under 24 hours of a report being submitted.
Average time to first response
Security fixes are batched and released quarterly. Last year, the TSC received a total of 80 submissions.
Node.js security submissions
This commitment to security wouldn't be possible without the support of the Open Source Security Foundation (OpenSSF). Through the OpenSSF-led Alpha-Omega project, funded by Microsoft, Google, and Amazon, Node.js has secured grants specifically dedicated to improving its security posture. Launched in 2022, the Alpha-Omega project aims to make critical open-source projects more secure by facilitating faster identification and resolution of vulnerabilities. This collaboration, along with Node.js' dedicated funding for security work, demonstrates a strong commitment to keeping Node.js users safe.
Total funding for security work
Key Features Released in Recent Years
Let's explore some of the features introduced in the last few years.
ESM
Node.js has embraced ECMAScript Modules (ESM). ESM offers a modern approach to structuring your code, making it cleaner and easier to maintain.
One key benefit of ESM is the ability to explicitly declare dependencies within import statements. This improves code readability and helps you keep track of what your project relies on. As a result, ESM is quickly becoming the preferred module format for new Node.js projects.
Below is a demo of how to use the ESM module in Node:
// addTwo.mjs
function addTwo(num) {
return num + 2;
}
export { addTwo };
// app.mjs
import { addTwo } from './addTwo.mjs';
// Prints: 6
console.log(addTwo(4));
Threads
Node also shipped worker threads, allowing users to offload complex calculations to separate threads. This frees up the main thread for handling user requests, leading to a smoother and more responsive user experience.
const {
Worker,
isMainThread,
setEnvironmentData,
getEnvironmentData,
} = require('node:worker_threads');
if (isMainThread) {
setEnvironmentData('Hello', 'World!');
const worker = new Worker(__filename);
} else {
console.log(getEnvironmentData('Hello')); // Prints 'World!'.
}
Fetch
Node.js now has a built-in implementation of the Fetch API, a modern and spec-compatible way to fetch resources over the network. This means you can write cleaner and more consistent code without relying on external libraries.
Node.js has also introduced several new features alongside Fetch to enhance web platform compatibility. These include:
Web Streams: Efficiently handle large data streams without overwhelming your application.
FormData: Effortlessly construct and send form data for web requests.
StructuredClone(): Create deep copies of complex data structures.
textEncoder() and textDecoder(): Seamlessly handle text encoding and decoding tasks.
Blob: Represent raw binary data for various use cases.
Combined with Fetch, these additions empower you to build modern web applications entirely within the Node.js environment.
const res = await fetch('https://example.com');
const json = await res.json();
console.log(json)
Promises
Node.js offers built-in Promise functionality, providing a cleaner and more structured way to handle the outcomes (success or failure) of asynchronous tasks.
Gone are the days of callback hell– with Promises, you can write code that flows more naturally and is easier to reason about.
Below is a practical example using the readFile method from the fs/promises module to demonstrate how Promises simplify asynchronous file reading:
import { readFile } from 'node:fs/promises';
try {
const filePath = new URL('./package.json', import.meta.url);
const contents = await readFile(filePath, { encoding: 'utf8' });
console.log(contents);
} catch (err) {
console.error(err.message);
}
Node-only Core Modules
Node.js has introduced a clear distinction between core and user-introduced modules with the introduction of the "node:" prefix for core modules.
This prefix acts like a label, instantly identifying a module as a core building block of Node.js. This distinction benefits developers in a few ways:
Reduced Confusion: No more mistaking core modules for user-created ones.
Simplified Selection: Easily pick the specific core module you need using the "node:" prefix.
This change also prevents users from claiming the npm registry's namespace with names that might conflict with future core modules, as shown below:
import test from 'test';
import assert from 'assert';
Vs
import test from 'node:test';
import assert from 'node:assert';
Watch
Before this feature was introduced, nodemon was the most popular package for watching changes in files.
Now, the --watch
flag offers:
Automatic File Watching: It keeps an eye on your imported files, ready to spring into action when any changes occur.
Instant Restarts: Whenever a watched file is modified, Node.js automatically restarts, ensuring your application reflects the latest updates.
Testing Synergy: The
--watch
flag plays nicely with your test runner, automatically re-running tests after a file change. This allows for a seamless development workflow with continuous feedback.
For even more granular control, the --watch-path
flag lets you specify exactly which files you want to keep an eye on.
AsyncLocalStorage
AsyncLocalStorage allows storing data throughout the lifetime of a web request or any other asynchronous duration. It is similar to thread-local storage in other languages.
AsyncLocalStorage empowers developers to create features like React Server Components, and it acts as the basis of the Next.js request store. These components streamline server-side rendering in React applications, ultimately improving the developer experience.
import http from 'node:http';
import { AsyncLocalStorage } from 'node:async_hooks';
const asyncLocalStorage = new AsyncLocalStorage();
function logWithId(msg) {
const id = asyncLocalStorage.getStore();
console.log(`${id !== undefined ? id : '-'}:`, msg);
}
let idSeq = 0;
http.createServer((req, res) => {
asyncLocalStorage.run(idSeq++, () => {
logWithId('start');
// Imagine any chain of async operations here
setImmediate(() => {
logWithId('finish');
res.end();
});
});
}).listen(8080);
http.get('http://localhost:8080');
http.get('http://localhost:8080');
// Prints:
// 0: start
// 1: start
// 0: finish
// 1: finish
WebCrypto
This standardized API provides a robust set of cryptographic tools directly within your Node.js environment.
With WebCrypto, you can leverage functionalities like:
Key Generation: Create strong cryptographic keys to secure your data.
Encryption and Decryption: Encrypt sensitive information for secure storage and transmission, and decrypt it when needed.
Digital Signatures: Sign data to ensure authenticity and prevent tampering.
Hashing: Generate unique fingerprints of data for verification and integrity checks.
By incorporating WebCrypto into your Node.js applications, you can significantly enhance their security posture and protect your users' data.
const {subtle} = require('node:crypto').webcrypto;
(async function () {
const key = await subtle.generateKey({
name:'HMAC',
hash: 'SHA-256',
length: 256
}, true, ['sign', 'verify'] )
const enc = new TextEncoder();
const message = enc.encode('I love cupcakes');
const digest = await subtle.sign({
name: 'HMAC'
}, key, message)
}) ()
Utilities
Node started doing a lot of utilities. The reason was that the core team believed users should not have to install new modules to perform basic utilities. Some of these utilities include the following.
Utils.ParseArgs()
Node.js offers a built-in utility called Utils.ParseArgs()
(or the parseArgs
function from the node:util
module) that simplifies the task of parsing command-line arguments in your applications. This eliminates the need for external modules, keeping your codebase leaner.
So, how does Utils.ParseArgs()
help? It takes the command-line arguments passed to your Node.js script and transforms them into a more usable format, typically an object. This object makes it easy to access and utilize these arguments within your code.
import { parseArgs } from 'node:util';
const args = ['-f', '--bar', 'b'];
const options = {
foo: {
type: 'boolean',
short: 'f',
},
bar: {
type: 'string',
},
};
const {
values,
positionals,
} = parseArgs({ args, options });
console.log(values, positionals);
// Prints: [Object: null prototype] { foo: true, bar: 'b' } []
Single Executable Applications
Single executable applications make it possible to distribute an application via Node. This is powerful in instances like building and distributing a CLI to users.
This feature injects application code into the Node binary. The Binaries can be distributed without Node/npm installation. Single CommonJS files are currently supported.
To simplify creating single executables, Node.js offers a helper module called postject, developed by Postman Labs.
Permission System
The Node.js process' access to system resources and the actions it can perform with them can be managed via permissions. Which modules other modules can access can also be managed by permissions.
process.permission.has('fs.write');
//true
process.permission.deny('fs.write', '/home/user');
process.permission.has('fs.write');
//true
process.permission.has('fs.write', '/home/user');
//false
Test Runner
It uses the node:test, the --test flag, and the npm test. It supports subtests, skip/only and lifecycle hook. It also supports function and timer mocking; the module mocking will be added soon.
It also renders code coverage via --experimental-test-coverage and reporters via the -test-reporter and -test-reporter-destination. Based on TTY, it defaults to spec, TAP, or stdout.
import test from 'node:test';
import test from 'test';
test('synchronous passing test', (t) => {
// This test passes because it does not throw an exception.
assert.strictEqual(1, 1);
});
test('synchronous failing test', (t) => {
// This test fails because it throws an exception.
assert.strictEqual(1, 2);
});
test('asynchronous passing test', async (t) => {
// This test passes because the Promise returned by the async
// function is settled and not rejected.
assert.strictEqual(1, 1);
});
test('asynchronous failing test', async (t) => {
// This test fails because the Promise returned by the async
// function is rejected.
assert.strictEqual(1, 2);
});
test('failing test using Promises', (t) => {
// Promises can be used directly as well.
return new Promise((resolve, reject) => {
setImmediate(() => {
reject(new Error('this will cause the test to fail'));
});
});
});
test('callback passing test', (t, done) => {
// done() is the callback function. When the setImmediate() runs, it invokes
// done() with no arguments.
setImmediate(done);
});
test('callback failing test', (t, done) => {
// When the setImmediate() runs, done() is invoked with an Error object and
// the test fails.
setImmediate(() => {
done(new Error('callback failure'));
});
});
A look at the latest Version
Here is a look at some of the features in the V22 release.
require(esm)
A new flag has been shipped, allowing developers to require the ESM module synchronously.
'use strict'
const {answer} = require('./esm.mjs')
console.log(answer)
Also, a new flag --experimental-detect-module
allows Node.js to detect whether a module is commonJS or esm. This new flag simplifies writing Bash scripting in Javascript.
WebSocket
WebSocket was one of the biggest feature requests for Node. This feature is also spec-compliant.
Contributing to Node.js
As an Open Source technology, Node.js is maintained primarily by volunteers and collaborators. Working on Node is challenging, so as its popularity grows, the more hands needed on deck to maintain it.
Node.js core collaborators maintain the nodejs/node GitHub repository.
The GitHub team for Node.js core collaborators is @nodejs/collaborators. Collaborators have:
Commit access to the nodejs/node repository
Access to the Node.js continuous integration (CI) jobs
Both collaborators and non-collaborators may propose changes to the Node.js source code. The mechanism to propose such a change is a GitHub pull request. Collaborators review and merge (land) pull requests.
Two collaborators must approve a pull request before the pull request can land. (One collaborator approval is enough if the pull request has been open for more than 7 days.) Approving a pull request indicates that the collaborator accepts responsibility for the change. Approval must be from collaborators who are not authors of the change.
If a collaborator opposes a proposed change, then the change cannot land. The exception is if the TSC votes to approve the change despite the opposition. Usually, involving the TSC is unnecessary.
Often, discussions or further changes result in collaborators removing their opposition.
Fundamentally, if you’d like to have a say in the future of Node.js, start contributing!
Wrapping Up
The rumors of Node.js' decline have been greatly exaggerated. This deep dive into the metrics paints a clear picture: Node.js is not only here to stay, but it's actively evolving to meet the demands of modern web development.
With its massive user base, thriving open-source community, and continuous stream of innovative features, Node.js remains a powerful and versatile platform. The recent additions of ESM, worker threads, Fetch API, and built-in modules demonstrate its commitment to staying at the forefront of technology.
Furthermore, Node.js prioritizes security with a dedicated team and rigorous processes. Its open collaboration model welcomes contributions from developers like you, ensuring a bright future for the platform.
So, whether you're a seasoned developer or just starting out, Node.js offers a compelling choice for building scalable and effective web applications. The extensive resources, active community, and commitment to continuous improvement make it a solid foundation for your next project.