11 Exciting Features in Node.js 20

Node.js 20 includes a permission model, stable test runner, V8 engine updates, and more. Learn about the 10 most exciting new features with code examples.

Node.js just keeps getting better! The latest release, Node.js 20, landed in April 2023 and it's packed with some really cool new features. As a long-time Node.js developer, I'm pumped to dive into the latest goodies. In this post, I'll walk through the most exciting updates in Node.js 20 and show you how they work with code examples. Get ready to level up your skills!

1. Secure Your Apps with the Permission Model

One of my favorite additions in Node.js 20 is the new experimental Permission Model. This provides a simple way to restrict file system access and prevent child processes/workers from being spawned.

In the past, any Node.js app could read, write, and delete files without explicit permission. This opened the door for malicious actions from third-party packages. The Permission Model fixes that by limiting access by default.

To enable it, use the --experimental-permission flag:

node --experimental-permission index.js

Now any attempt to read, write, or spawn processes will throw an error like:

Error: Access to this API has been restricted

To grant permissions, use flags like --allow-fs-read and --allow-child-process. For example:

node --experimental-permission \
  --allow-fs-read=/tmp \
  --allow-child-process \
  index.js

You can also check for permissions at runtime:

if (process.permission.has('fs.read')) {
  // read files
}

The Permission Model enables building more secure apps. No more worrying about third-party packages wiping out files!

2. Run Tests with the Stable Test Runner

Testing just got a whole lot easier with the new built-in test runner module. This removes the need for external libraries like Mocha or Jest.

To use it, first import test and assert from node:

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

Then write test cases with the test function:

test('2 + 2 equals 4', () => {
  assert.equal(2 + 2, 4);
});

Run the tests using the --test CLI flag:

node --test test.js 

The test runner searches for test files, runs them in parallel, and prints results. No more fussing with configuring third-party tools!

Some other handy features include:

  • describe blocks for grouping tests
  • before/after hooks
  • skip to ignore tests
  • --watch mode to auto-run on changes

The stable test runner provides an excellent testing experience out-of-the-box.

3. Upgrade to JavaScript Engine V8 11.3

Node.js 20 ships with the latest V8 engine, version 11.3. This update unlocks some useful JavaScript enhancements.

One of my favorites is resizable ArrayBuffer objects. Instead of setting a fixed buffer size upfront, you can now resize dynamically with the resize() method:

const buffer = new ArrayBuffer(10); // Initial size of 10 bytes

// ... add some data ...

if (buffer.resizable) {
  buffer.resize(100); // Increase size to 100 bytes
}

SharedArrayBuffers work similarly with the grow() method. No more pre-allocating unnecessarily large buffers!

V8 11.3 also adds:

  • String.prototype.isWellFormed() for checking Unicode strings
  • Improved regular expression Unicode support
  • Async stack trace support
  • Various performance tweaks

Overall the latest V8 brings faster and more consistent execution along with useful language enhancements.

4. Build Single Executable Apps

Bundling an entire Node.js app into a single executable file is now possible with experimental Single Executable Apps (SEA).

This allows distributing apps without requiring the end user to install Node.js, since the runtime is embedded within the executable.

Here's a simple example to create a SEA on Linux:

First, define the input and output in a JSON config:

// sea-config.json
{
  "main": "index.js",
  "output": "app.blob" 
}

Use the config to generate a Node.js blob:

node --experimental-sea-config sea-config.json  

This outputs app.blob containing the bundled app code.

Next, copy the Node.js executable and inject the blob:

cp $(command -v node) myapp
npx postject myapp NODE_SEA_BLOB app.blob 

Now myapp will execute as a standalone binary!

SEA allows packaging apps for distribution without complex bundlers like Webpack. It's experimental but shows promise for workspaces and IoT devices.

5. Recursively List Files with fs.readdir()

The fs.readdir() method now supports recursively reading all files in nested directories.

Pass { recursive: true } to enable recursive mode:

import { readdir } from 'node:fs';

const files = await readdir('/path', { recursive: true });

// files contains ALL files including subdirs

This simplifies recursively processing directories without needing external modules:

for (const file of files) {
  // parse, modify, etc
}

Of course, synchronous fs.readdirSync() also supports the recursive option too.

No more reaching for recursive fs modules like readdirp!

6. Import Meta URL Resolution

ES modules have a new synchronous import.meta.resolve() method for resolving a relative URL.

For example, to resolve a module URL relative to the current file:

import { resolve } from 'import.meta';

const url = resolve('./file.js');

This returns an absolute URL string that can be used to dynamically import modules:

const module = await import(url);

The resolve() function is handy for getting a file's path to load dynamically. Previous workarounds involved await syntax which was confusing.

Synchronous resolution aligns better with static module imports and avoids callback hell!

7. Official ARM 64-bit Windows Support

Windows on ARM devices like the Surface Pro X can now run Node.js natively.

Pre-built binaries are available for download including native ARM MSI installers. Node.js 20 is also tested on ARM64 Windows builders to prevent regressions.

ARM architectures are becoming more popular with advantages like:

  • Improved battery life
  • Lower cost
  • Smaller size

With first-class support, you can develop Node.js apps knowing they'll run fast on the latest ARM Windows laptops and tablets.

8. Progress on WASI Support

The WebAssembly System Interface (WASI) allows running sandboxed WebAssembly modules outside the browser.

Node.js implements WASI to securely execute WASM without granting full access to the host system.

Recent progress includes:

  • No longer requires a command line flag
  • Standardized on a required WASI version

This brings WASI closer to stability and production readiness.

In the future, combining WASM and WASI could allow distributing safe "binary" modules for Node.js. Very promising possibilities!

9. Improved EventTarget Performance

EventTarget is used extensively within Node.js for asynchronous events and networking.

Initializing EventTarget previously involved some costly validity checks of the this context. These have been optimized in Node.js 20.

For example, measuring new EventTarget() shows about a 2x speedup:

// Node 18
1,853,163 ops/sec

// Node 20 
3,370,759 ops/sec  

Faster EventTarget initialization improves performance across all subsystems that depend on it including networking APIs.

It's an excellent example of constantly optimizing Node's internals for speed.

10. Web Crypto API Interoperability

The Web Crypto API provides low-level cryptographic functions like encryption and decryption.

Node.js 20 updates the Web Crypto implementation to better align with web standards. For example:

  • Argument validation and coercion
  • Default values
  • Error types

This improves cross-compatibility when using the Web Crypto API across different JavaScript environments.

As security grows in importance, it's great to see continued progress on shared cryptographic primitives between Node.js and the browser.

11. Diagnostics Channel Support

A new diagnostics_channel module provides a simple pub/sub channel for application metrics and logging.

For example, you can create a channel and subscribe to messages:

import diagnostics_channel from 'diagnostics_channel';

const channel = diagnostics_channel.channel('app-metrics');

channel.subscribe(msg => {
  console.log(msg);  
});

Elsewhere in your app, publish messages to the channel:

channel.publish({
  type: 'HTTP_REQUEST',
  duration: 120 
});

The subscriber will receive each message object.

This offers a handy way to implement distributed tracing and performance monitoring in Node.js services. Diagnostics data can be easily aggregated and analyzed.

Channels transmit data efficiently using shared memory without the overhead of socket connections. Much better than rolling your own pub/sub middleware!

Summary

Node.js 20 moves the platform forward with important security, testing, performance, and ecosystem improvements.

Some highlights include:

  • Permission Model for more secure apps
  • Stable built-in testing framework
  • Single Executable Apps for easier distribution
  • Recursive filesystem access
  • Diagnostics channels
  • WASI and EventTarget enhancements
  • V8 engine upgrade

As Node.js matures, the core runtime is getting faster while providing more robust features out-of-the-box. The team keeps delivering awesome updates with each new release. We're sure to see more exciting progress in the year ahead!

So give Node.js 20 a spin and see what new capabilities you can build into your next web project.

Subscribe to JS Dev Journal

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe