avatarJennifer Fu

Summary

Node.js 21 was released on October 17, 2023, and comes with seven major features, including ES modules default, WebSocket client, test runner enhancements, and more.

Abstract

Node.js 21 is the current release and was launched on October 17, 2023. This release will remain the Current release for six months before becoming unsupported. Node.js releases every six months, giving library authors time to add support for new features. The LTS (Long Term Support) release typically guarantees critical bug fixes for 30 months and is recommended for production applications. Node.js 21 includes seven major features:

  1. The experimental flag to specify module default
  2. The experimental flag to support WebSocket client
  3. The flag to specify test runner concurrency level
  4. Test runner supports passing globs
  5. fs with the flush option to writeFile() functions
  6. Stable fetch and WebStreams
  7. Update the V8 JavaScript engine to 11.8

These features allow developers to take advantage of new functionality and improvements in Node.js.

Bullet points

  • Node.js 21 is the Current release and will remain so for six months.
  • Node.js 21 has seven major features, including ES modules default, WebSocket client, test runner enhancements, and more.
  • The experimental flag to specify module default allows developers to use ES modules as the default instead of CommonJS.
  • The experimental flag to support WebSocket client allows developers to use the WebSocket protocol in Node.js applications.
  • The flag to specify test runner concurrency level allows developers to control the maximum number of test files executed concurrently.
  • Test runner supports passing globs, allowing developers to specify multiple filenames as test files using glob patterns.
  • fs with the flush option to writeFile() functions allows developers to force the data to be flushed to permanent storage after a successful write operation.
  • Stable fetch and WebStreams allow developers to use the Fetch API and Web Streams API in Node.js applications.
  • Update the V8 JavaScript engine to 11.8 includes new features, such as ArrayBuffer.prototype.transfer and ArrayBuffer.prototype.transferToFixedLength, Object.groupBy, and Map.groupBy.

7 Major Features of Node.js 21

Details of Node.js 21’s new features, including ES modules default, WebSocket client, test runner enhancements, and more

Photo by Anton Ivanchenko on Unsplash

Node.js major release is rolled out every six months. The new release becomes the Current release for six months, which gives library authors time to add support for them.

After six months, odd-numbered releases, such as 21, become unsupported, and even-numbered releases, such as 20, move to the Active LTS (long-term support) status and are ready for general use.

LTS release typically guarantees that critical bugs will be fixed for 30 months. Production applications should only use Active LTS or Maintenance LTS releases.

Node.js 21 was released on October 17, 2023. It becomes the Current release. It comes with 7 major features:

  • The experimental flag to specify module default
  • The experimental flag to support WebSocket client
  • The flag to specify test runner concurrency level
  • Test runner supports passing globs
  • fs with the flush option to writeFile() functions
  • stable fetch and WebStreams
  • Update the V8 JavaScript engine to 11.8

Let’s explore what they are and how to use them.

Use NVM To Explore Node

In a previous article, we provided instructions on using NVM (Node Version Manager) to manage Node.js and NPM versions.

Run the command to install the latest Node.js 21:

% nvm install 21
Downloading and installing node v21.1.0...
Downloading https://nodejs.org/dist/v21.1.0/node-v21.1.0-darwin-x64.tar.xz...
######################################################################### 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v21.1.0 (npm v10.2.0)

On any window, run the command to use Node.js 21:

% nvm use 21
Now using node v21.1.0 (npm v10.2.0)

Now, we’re ready to explore:

% node -v
v21.1.0

The experimental flag to specify module default

We have explained that CommonJS (CJS) is the standard that is used by Node.js to encapsulate JavaScript in modules. CJS uses require() function and module.exports.

ES Modules (ESM) become the official standard used in JavaScript since ES2015. It is widely used in JavaScript client development. It is also adopted by TypeScript that is a superset with additional types. ESM uses import and export statements to handle modules.

Create the following file using export:

// index.js

export const message = 'Hello, World!';
console.log(message);

Since CJS is the default for Node.js, the command node index.js fails:

% node index.js
(node:67887) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/jenniferfu/node21/index.js:1
export const message = 'Hello, World!';
^^^^^^

SyntaxError: Unexpected token 'export'
    at internalCompileFunction (node:internal/vm:77:18)
    at wrapSafe (node:internal/modules/cjs/loader:1288:20)
    at Module._compile (node:internal/modules/cjs/loader:1340:27)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Node.js v21.1.0

Before Node.js 21, there are 3 ways to treat the code as ES modules:

  1. Files with an .mjs extension.

Renaming index.js to index.mjs works:

% node index.mjs
Hello, World!

2. Files with a .js extension when the nearest parent package.json file contains a top-level "type" field with a value of "module".

{
  "name": "my-project",
  "version": "1.0.0",
  "type": "module",
  "dependencies": {
    ...
  },
  "scripts": {
    ...
  },
  ...
}

3. Strings passed in as an argument to --eval, or piped to node via STDIN, with the flag --input-type=module.

% node --input-type=module --eval "export const message = 'Hello, World!'; console.log(message);"
Hello, World!
% echo "export const message = 'Hello, World!'; console.log(message);" | node --input-type=module 
Hello, World!

On the other hand, Node.js can explicitly define the CommonJS type with the following methods:

  • Files with a .cjs extension.
  • Files with a .js extension when the nearest parent package.json file contains a top-level field "type" with a value of "commonjs".
  • Strings passed in as an argument to --eval or --print, or piped to node via STDIN, with the flag --input-type=commonjs.

From Node.js 21, a new experimental flag --experimental-default-type is introduced to specify module default. It supports values commonjs (the default) or module. With --experimental-default-type=module, Node.js interprets the following as ES modules:

  • String input provided via --eval or STDIN, if --input-type is unspecified.
  • Files ending in .js or with no extension, if there is no package.json file present in the same folder or any parent folder.
  • Files ending in .js or with no extension, if the nearest parent package.json field lacks a type field; unless the folder is inside a node_modules folder.

It becomes the fourth way to run index.js as ES modules.

% node --experimental-default-type=module index.js
Hello, World!

The experimental flag to support WebSocket client

WebSocket is a computer communications protocol, providing full-duplex communication channels over a single TCP connection. The WebSocket protocol was standardized by the IETF as RFC 6455 in December 2011, and the WebSocket API was specified by the W3C in December 2009. It has been a while since all major browsers support the WebSocket API.

However, Node.js adds WebSocket client from version 21, with a new experimental flag --experimental-websocket.

Here is the WebSocket definition:

interface WebSocket : EventTarget {
  constructor(USVString url, optional (DOMString or sequence<DOMString>) protocols = []);
  readonly attribute USVString url;

  // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSING = 2;
  const unsigned short CLOSED = 3;
  readonly attribute unsigned short readyState;
  readonly attribute unsigned long long bufferedAmount;

  // networking
  attribute EventHandler onopen;
  attribute EventHandler onerror;
  attribute EventHandler onclose;
  readonly attribute DOMString extensions;
  readonly attribute DOMString protocol;
  undefined close(optional [Clamp] unsigned short code, optional USVString reason);

  // messaging
  attribute EventHandler onmessage;
  attribute BinaryType binaryType;
  undefined send((BufferSource or Blob or USVString) data);
};

Postman has a live WebSocket echo service that echos the received message at wss://ws.postman-echo.com/raw. We write the following WebSocket client to connect to the Postman’s WebSocket server:

// client.js

const ws = new WebSocket('wss://ws.postman-echo.com/raw');

ws.addEventListener('message', (event) => {
  console.log('Received:', event.data);
});

ws.addEventListener('open', () => {
  let i = 0;
  setInterval(() => {
    const text = `Message ${i++}`;
    console.log('Sent:', text);
    ws.send(text);
  }, 1000);
});

Execute the code without the flag, and it reports an error:

% node client.js
/Users/jenniferfu/node21/client.js:1
const ws = new WebSocket('wss://ws.postman-echo.com/raw');
           ^

ReferenceError: WebSocket is not defined
    at Object.<anonymous> (/Users/jenniferfu/node21/client.js:1:12)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Node.js v21.1.0

It works with the flag --experimental-websocket:

% node --experimental-websocket client.js
(node:93634) [UNDICI-WS] Warning: WebSockets are experimental, expect them to change at any time.
(Use `node --trace-warnings ...` to show where the warning was created)
Sent: Message 0
Received: Message 0
Sent: Message 1
Received: Message 1
Sent: Message 2
Received: Message 2
Sent: Message 3
Received: Message 3
Sent: Message 4
Received: Message 4
Sent: Message 5
Received: Message 5

The flag to specify test runner concurrency level

The test_runner module was introduced in Node.js 18, and it was marked stable at Node.js 20.

By default, node --test will run all files matching these patterns:

  • **/*.test.?(c|m)js
  • **/*-test.?(c|m)js
  • **/*_test.?(c|m)js
  • **/test-*.?(c|m)js
  • **/test.?(c|m)js
  • **/test/**/*.?(c|m)js

Here is test-1.js:

// test-1.js

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

test('synchronous passing test', (t) => {
  assert.strictEqual(1, 1);
});

Here is test-2.js:

// test2.js:

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

test('synchronous failing test', (t) => {
  assert.strictEqual(1, 2);
});

The flag --test-concurrency has been introduced in Node.js 21 to specify the maximum number of test files that the test runner CLI will execute concurrently. The default value is os.availableParallelism() - 1.

It took 154.337642 ms to run one file at a time:

 % node --experimental-default-type=module --test --test-concurrency=1
✔ synchronous passing test (0.889205ms)
✖ synchronous failing test (1.91626ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  1 !== 2

      at TestContext.<anonymous> (file:///Users/jenniferfu/node21/test-2.js:5:10)
      at Test.runInAsyncScope (node:async_hooks:206:9)
      at Test.run (node:internal/test_runner/test:631:25)
      at Test.start (node:internal/test_runner/test:542:17)
      at startSubtest (node:internal/test_runner/harness:216:17) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 1,
    expected: 2,
    operator: 'strictEqual'
  }

ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 154.337642

✖ failing tests:

test at file:/Users/jenniferfu/node21/test-2.js:4:1
✖ synchronous failing test (1.91626ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  1 !== 2

      at TestContext.<anonymous> (file:///Users/jenniferfu/node21/test-2.js:5:10)
      at Test.runInAsyncScope (node:async_hooks:206:9)
      at Test.run (node:internal/test_runner/test:631:25)
      at Test.start (node:internal/test_runner/test:542:17)
      at startSubtest (node:internal/test_runner/harness:216:17) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 1,
    expected: 2,
    operator: 'strictEqual'
  }

But, it took 91.93992 ms to run two files concurrently:

% node --experimental-default-type=module --test --test-concurrency=2
✔ synchronous passing test (0.8897ms)
✖ synchronous failing test (1.848529ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  1 !== 2

      at TestContext.<anonymous> (file:///Users/jenniferfu/node21/test-2.js:5:10)
      at Test.runInAsyncScope (node:async_hooks:206:9)
      at Test.run (node:internal/test_runner/test:631:25)
      at Test.start (node:internal/test_runner/test:542:17)
      at startSubtest (node:internal/test_runner/harness:216:17) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 1,
    expected: 2,
    operator: 'strictEqual'
  }

ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 91.93992

✖ failing tests:

test at file:/Users/jenniferfu//node21/test-2.js:4:1
✖ synchronous failing test (1.848529ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  1 !== 2

      at TestContext.<anonymous> (file:///Users/jenniferfu/node21/test-2.js:5:10)
      at Test.runInAsyncScope (node:async_hooks:206:9)
      at Test.run (node:internal/test_runner/test:631:25)
      at Test.start (node:internal/test_runner/test:542:17)
      at startSubtest (node:internal/test_runner/harness:216:17) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 1,
    expected: 2,
    operator: 'strictEqual'
  }

Test runner supports passing globs

node --test runs all files matching predefined patterns. Alternatively, one or more filenames can be specified. From Node.js 21, filenames can be specified as globs, i.e. the name containing *.

Execute the command with the glob filename, where **/test*.js includes all js files whose name starts with test, under the current directory or subdirectories:

% node --experimental-default-type=module --test **/test*.js
✔ synchronous passing test (0.870144ms)
✖ synchronous failing test (1.984882ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  1 !== 2

      at TestContext.<anonymous> (file:///Users/jenniferfu/node21/test2.js:5:10)
      at Test.runInAsyncScope (node:async_hooks:206:9)
      at Test.run (node:internal/test_runner/test:631:25)
      at Test.start (node:internal/test_runner/test:542:17)
      at startSubtest (node:internal/test_runner/harness:216:17) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 1,
    expected: 2,
    operator: 'strictEqual'
  }

ℹ tests 2
ℹ suites 0
ℹ pass 1
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 83.026188

✖ failing tests:

test at file:/Users/jenniferfu/node21/test2.js:4:1
✖ synchronous failing test (1.984882ms)
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  1 !== 2

      at TestContext.<anonymous> (file:///Users/jenniferfu/node21/test2.js:5:10)
      at Test.runInAsyncScope (node:async_hooks:206:9)
      at Test.run (node:internal/test_runner/test:631:25)
      at Test.start (node:internal/test_runner/test:542:17)
      at startSubtest (node:internal/test_runner/harness:216:17) {
    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 1,
    expected: 2,
    operator: 'strictEqual'
  }

fs with the flush option to writeFile() functions

fs.writeFile(file, data[, options]) writes data to files, where parameters are defined as follows:

  • file: <string> | <Buffer> | <URL> | <FileHandle> filename or FileHandle
  • data: | | | | | |
  • options: |
  • encoding: <string> | <null>, default: 'utf8'
  • mode: <integer>, default: 0o666
  • flag: <string>, default: 'w'.
  • flush: <boolean>, if all data is successfully written to the file, and flush is true, filehandle.sync() is used to flush the data. Default: false.
  • signal: allows aborting an in-progress writeFile.

The flush option is added in Node.js 21.

The following code configures flush to be true:

// write.js

import { writeFile } from 'node:fs/promises';
import { Buffer } from 'node:buffer';

try {
  const data = new Uint8Array(Buffer.from('Hello, World!'));
  const promise = writeFile('message.txt', data, { flush: true });
  await promise;
} catch (err) {
  console.error(err);
}

Execute the code, node --experimental-default-type=module write.js, and it generates a file, message.txt.

Here is the content of message.txt:

Hello, World!

Without setting flush to be true, it is possible that data is not immediately flushed to permanent storage. This allows subsequent read operations to see stale data. The flush option to the fs.writeFile family of functions forces the data to be flushed at the end of a successful write operation.

Stable fetch and WebStreams

Node.js 21 includes an important change to promote fetch and webstreams from experimental to stable.

  • The fetch API starts the process of fetching a resource from the network, returning a promise which is fulfilled once the response is available.
  • The Web Streams API enables programmatically access streams of data received over the network and process them by chunks, without needing to generate a buffer, string, or blob.

We have provided examples for both APIs in Node.js 18 article.

Update the V8 JavaScript engine to 11.8

Node.js 21 has updated the V8 JavaScript engine to V8 11.8, which includes a few new features. Let’s take a look at two of them.

ArrayBuffer.prototype.transfer and ArrayBuffer.prototype.transferToFixedLength

The transfer() method of ArrayBuffer instances creates a new ArrayBuffer with the same byte content as this buffer, then detaches this buffer.

ArrayBuffer.prototype.transfer can be called as transfer() or transfer(newByteLength). The optional parameter newByteLength specifies byteLength of the new ArrayBuffer. The default value is the byteLength of this ArrayBuffer.

  • If newByteLength is smaller than the byteLength of this ArrayBuffer, the overflowing bytes are dropped.
  • If newByteLength is larger than the byteLength of this ArrayBuffer, the extra bytes are filled with zeros.
  • If this ArrayBuffer is resizable, newByteLength must not be greater than its maxByteLength.

The following are examples of transfer():

// transfer.js

// create an ArrayBuffer and write a few bytes
const buffer = new ArrayBuffer(4, { maxByteLength: 8 });
console.log(buffer.byteLength); 
console.log(buffer.detached); 
const view = new Uint8Array(buffer);
view[1] = 1;
view[3] = 3;
console.log(view);

// copy the buffer to the same size buffer
const buffer2 = buffer.transfer();
console.log(buffer.detached); 
console.log(buffer2.byteLength); 
console.log(buffer2.detached); 
const view2 = new Uint8Array(buffer2);
console.log(view2); 

// copy the buffer to a smaller size buffer
const buffer3 = buffer2.transfer(2);
console.log(buffer2.detached); 
console.log(buffer3.byteLength); 
console.log(buffer3.detached); 
const view3 = new Uint8Array(buffer3);
console.log(view3); 

// copy the buffer to a larger size buffer within maxByteLength
const buffer4 = buffer3.transfer(4);
console.log(buffer3.detached); 
console.log(buffer4.byteLength);
console.log(buffer4.detached); 
const view4 = new Uint8Array(buffer4);
console.log(view4); 

// copy the buffer to a larger size buffer beyond maxByteLength
const buffer5 = buffer4.transfer(16);
console.log(buffer4.detached); 
console.log(buffer5.byteLength);
console.log(buffer5.detached); 
const view5 = new Uint8Array(buffer5);
console.log(view5); 

Execute the code, and we get the following output:

% node transfer.js                                
4
false
Uint8Array(4) [ 0, 1, 0, 3 ]
true
4
false
Uint8Array(4) [ 0, 1, 0, 3 ]
true
2
false
Uint8Array(2) [ 0, 1 ]
true
4
false
Uint8Array(4) [ 0, 1, 0, 0 ]
/Users/jenniferfu/funStuff/node21/transfer.js:37
const buffer5 = buffer4.transfer(16);
                        ^

RangeError: Invalid array buffer length
    at ArrayBuffer.transfer (<anonymous>)
    at Object.<anonymous> (/Users/jenniferfu/funStuff/node21/transfer.js:37:25)
    at Module._compile (node:internal/modules/cjs/loader:1376:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Module._load (node:internal/modules/cjs/loader:1023:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
    at node:internal/main/run_main_module:28:49

Node.js v21.1.0

ArrayBuffer.prototype.transferToFixedLength can be called as transferToFixedLength() or transferToFixedLength(newByteLength). Unlike transfer(), transferToFixedLength() always creates a non-resizable ArrayBuffer. This means newByteLength can be larger than the maxByteLength, even if this ArrayBuffer is resizable.

The following code shows examples of transferToFixedLength():

// transferToFixedLength.js

// create an ArrayBuffer and write a few bytes
const buffer = new ArrayBuffer(4, { maxByteLength: 8 });
console.log(buffer.byteLength); 
console.log(buffer.detached); 
const view = new Uint8Array(buffer);
view[1] = 1;
view[3] = 3;
console.log(view);

// copy the buffer to the same size buffer
const buffer2 = buffer.transferToFixedLength();
console.log(buffer.detached); 
console.log(buffer2.byteLength); 
console.log(buffer2.detached); 
const view2 = new Uint8Array(buffer2);
console.log(view2); 

// copy the buffer to a smaller size buffer
const buffer3 = buffer2.transferToFixedLength(2);
console.log(buffer2.detached); 
console.log(buffer3.byteLength); 
console.log(buffer3.detached); 
const view3 = new Uint8Array(buffer3);
console.log(view3); 

// copy the buffer to a larger size buffer within maxByteLength
const buffer4 = buffer3.transferToFixedLength(8);
console.log(buffer3.detached); 
console.log(buffer4.byteLength);
console.log(buffer4.detached); 
const view4 = new Uint8Array(buffer4);
console.log(view4); 

// copy the buffer to a larger size buffer beyond maxByteLength
const buffer5 = buffer4.transferToFixedLength(16);
console.log(buffer4.detached); 
console.log(buffer5.byteLength);
console.log(buffer5.detached); 
const view5 = new Uint8Array(buffer5);
console.log(view5); 

Execute the code, and we get the following output:

% node transfer.js
4
false
Uint8Array(4) [ 0, 1, 0, 3 ]
true
4
false
Uint8Array(4) [ 0, 1, 0, 3 ]
true
2
false
Uint8Array(2) [ 0, 1 ]
true
8
false
Uint8Array(8) [
  0, 1, 0, 0,
  0, 0, 0, 0
]
true
16
false
Uint8Array(16) [
  0, 1, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0,
  0, 0, 0, 0
]

Object.groupBy and Map.groupBy

Object.groupBy() is a static method that groups the elements of a given iterable according to the string values returned by a callback function. The returned object has separate properties for each group, containing arrays with the elements in the group.

The method is defined as Object.groupBy(items, callbackFn):

  • items: It is an iterable, such as an Array, whose elements will be grouped.
  • callbackFn: It is a function to execute for each element in the iterable. It should return a value that can get coerced into a property key, string or symbol, indicating the group of the current element.

Object.groupBy() should be used when group names can be represented by strings. Otherwise, we should use Map.groupBy().

The following code shows examples of Object.groupBy() and Map.groupBy():

// groupBy.js

const fruits = [
  { name: 'watermelon', size: 'big', quantity: 1 },
  { name: 'bananas', size: 'medium', quantity: 5 },
  { name: 'apple', size: 'medium', quantity: 2 },
  { name: 'peach', size: 'medium', quantity: 0 },
  { name: 'grape', size: 'small', quantity: 10 },
  { name: 'unknown' },
];

// group by the item key
console.log(Object.groupBy(fruits, ({ size }) => size));

// group by index
console.log(
  Object.groupBy(fruits, (_, index) => (index <= 1 ? 'first two' : 'rest'))
);

// group by non-string key
console.log(Map.groupBy(fruits, ({ name }) => ({
  johnLike: ['watermelon', 'apple'].includes(name),
  maryLike: ['watermelon', 'peach'].includes(name),
})));

Execute the code, and we get the following output:

% node groupBy.js
[Object: null prototype] {
  big: [ { name: 'watermelon', size: 'big', quantity: 1 } ],
  medium: [
    { name: 'bananas', size: 'medium', quantity: 5 },
    { name: 'apple', size: 'medium', quantity: 2 },
    { name: 'peach', size: 'medium', quantity: 0 }
  ],
  small: [ { name: 'grape', size: 'small', quantity: 10 } ],
  undefined: [ { name: 'unknown' } ]
}
[Object: null prototype] {
  'first two': [
    { name: 'watermelon', size: 'big', quantity: 1 },
    { name: 'bananas', size: 'medium', quantity: 5 }
  ],
  rest: [
    { name: 'apple', size: 'medium', quantity: 2 },
    { name: 'peach', size: 'medium', quantity: 0 },
    { name: 'grape', size: 'small', quantity: 10 },
    { name: 'unknown' }
  ]
}
Map(6) {
  { johnLike: true, maryLike: true } => [ { name: 'watermelon', size: 'big', quantity: 1 } ],
  { johnLike: false, maryLike: false } => [ { name: 'bananas', size: 'medium', quantity: 5 } ],
  { johnLike: true, maryLike: false } => [ { name: 'apple', size: 'medium', quantity: 2 } ],
  { johnLike: false, maryLike: true } => [ { name: 'peach', size: 'medium', quantity: 0 } ],
  { johnLike: false, maryLike: false } => [ { name: 'grape', size: 'small', quantity: 10 } ],
  { johnLike: false, maryLike: false } => [ { name: 'unknown' } ]
}

Conclusion

Node.js 21 has several new features and improvements, including ES modules default, WebSocket client, test runner enhancements, fs.writeFile()’s flush option, stable fetch and WebStreams, and V8 JavaScript engine 11.8 features. It is the Current release until node.js 22 is released.

If you want to check out features of previous releases, take a look at the following articles:

Here is a list of npm features:

Thanks for reading.

Want to Connect?

If you are interested, check out my directory of web development articles.

In Plain English

Thank you for being a part of our community! Before you go:

Nodejs
JavaScript
Web Development
Programming
Software Development
Recommended from ReadMedium