Node.js 20 is now available!

The Node.js Project

We're excited to announce the release of Node.js 20! Highlights include the new Node.js Permission Model,a synchronous import.meta.resolve, a stable test_runner, updates of the V8 JavaScript engine to 11.3, Ada to 2.0, and more!

The project continues to make progress across a number of areas, with many new features and fixes flowing into existing LTS releases. For that reason, the changes outlined in the changelog for Node.js 20 only represent a small subset of the features and work since the last major release. This blog post will add some additional context on the larger body of work in relation to those changes.

You can read more about our release policy at https://github.com/nodejs/release.

To download Node.js 20.0.0, visit: https://nodejs.org/download/current/. You can find the release post at https://nodejs.org/blog/release/v20.0.0, which contains the full list of commits included in this release.

As a reminder, Node.js 20 will enter long-term support (LTS) in October, but until then, it will be the "Current" release for the next six months. We encourage you to explore the new features and benefits offered by this latest release and evaluate their potential impact on your applications.

Notable Changes

Permission Model

The Node.js Permission Model is an experimental mechanism for restricting access to specific resources during execution.

In this first release containing the Permission Model, the features come with the following abilities:

The available permissions are documented by the --experimental-permission flag.

When starting Node.js with --experimental-permission, the ability to access the file system, spawn processes, and use node:worker_threads will be restricted.

Developers using Node.js now have more control over file system access with the introduction of the --allow-fs-read and --allow-fs-write flags. These experimental features allow for more granular control over which parts of the file system can be accessed by Node.js processes.

To enable these flags, developers can use the --experimental-permission flag along with the desired permissions. For example, running the following command allows for both read and write access to the entire file system:

$ node --experimental-permission --allow-fs-read=* --allow-fs-write=* index.js

Developers can also specify specific paths for file system access by passing in comma-separated values to the flags. For example, the following command allows for write access to the /tmp/ folder:

$ node --experimental-permission --allow-fs-write=/tmp/ --allow-fs-read=/home/index.js index.js

Wildcard patterns can also be used to allow for access to multiple files or folders at once. For example, the following command allows for read access to all files and folders in the /home/ directory that start with test:

$ node --experimental-permission --allow-fs-read=/home/test* index.js

When the Permission Model is enabled, the new permission property of the process object can be used to check if a certain permission has been granted at runtime.

var process: NodeJS.Processprocess.NodeJS.Process.permission: NodeJS.ProcessPermission
This API is available through the [--permission](https://nodejs.org/api/cli.html#--permission) flag. `process.permission` is an object whose methods are used to manage permissions for the current process. Additional documentation is available in the [Permission Model](https://nodejs.org/api/permissions.html#permission-model).
@sincev20.0.0
permission
.NodeJS.ProcessPermission.has(scope: string, reference?: string): boolean
Verifies that the process is able to access the given scope and reference. If no reference is provided, a global scope is assumed, for instance, `process.permission.has('fs.read')` will check if the process has ALL file system read permissions. The reference has a meaning based on the provided scope. For example, the reference when the scope is File System means files and folders. The available scopes are: * `fs` - All File System * `fs.read` - File System read operations * `fs.write` - File System write operations * `child` - Child process spawning operations * `worker` - Worker thread spawning operation ```js // Check if the process has permission to read the README file process.permission.has('fs.read', './README.md'); // Check if the process has read permission operations process.permission.has('fs.read'); ```
@sincev20.0.0
has
('fs.write'); // true
var process: NodeJS.Processprocess.NodeJS.Process.permission: NodeJS.ProcessPermission
This API is available through the [--permission](https://nodejs.org/api/cli.html#--permission) flag. `process.permission` is an object whose methods are used to manage permissions for the current process. Additional documentation is available in the [Permission Model](https://nodejs.org/api/permissions.html#permission-model).
@sincev20.0.0
permission
.NodeJS.ProcessPermission.has(scope: string, reference?: string): boolean
Verifies that the process is able to access the given scope and reference. If no reference is provided, a global scope is assumed, for instance, `process.permission.has('fs.read')` will check if the process has ALL file system read permissions. The reference has a meaning based on the provided scope. For example, the reference when the scope is File System means files and folders. The available scopes are: * `fs` - All File System * `fs.read` - File System read operations * `fs.write` - File System write operations * `child` - Child process spawning operations * `worker` - Worker thread spawning operation ```js // Check if the process has permission to read the README file process.permission.has('fs.read', './README.md'); // Check if the process has read permission operations process.permission.has('fs.read'); ```
@sincev20.0.0
has
('fs.write', '/home/nodejs/protected-folder'); // true

It is important to note that these features are still experimental and may change in future releases of Node.js

Furthermore can be found in Permission Model documentation.

The Permission Model was a contribution by Rafael Gonzaga in #44004.

Custom ESM loader hooks nearing stable

Custom ES module lifecycle hooks supplied via loaders (--experimental-loader=./foo.mjs) now run in a dedicated thread, isolated from the main thread. This provides a separate scope for loaders and ensures no cross-contamination between loaders and application code.

In alignment with browser behavior, import.meta.resolve() now returns synchronously; note that resolve hooks in user loaders can remain async if the loader author desires, and import.meta.resolve will still return synchronously in application code.

These changes were the last outstanding items before marking ESM loaders as stable. Once some time has gone by without significant bugs reported by the community, we intend to mark the loaders flag, import.meta.resolve and the resolve and load hooks as stable. This should enable more widespread adoption of ESM, as important constituencies such as instrumentation vendors will have a stable API on which to build analytics and reporting libraries.

Contributed by Anna Henningsen, Antoine du Hamel, Geoffrey Booth, Guy Bedford, Jacob Smith, and Michaël Zasso in #44710.

V8 11.3

As per usual a new version of the V8 engine is included in Node.js (updated to version 11.3, which is part of Chromium 113) bringing improved performance and new language features including:

The V8 update was a contribution by Michaël Zasso in #47251.

Stable Test Runner

The recent update to Node.js, version 20, includes an important change to the test_runner module. The module has been marked as stable after a recent update. The stable test runner includes the building blocks for authoring and running tests, including:

  • describe, it/test and hooks to structure test files
  • mocking
  • watch mode
  • node --test for running multiple test files in parallel

The test runner also includes some parts that are not yet stable, including reporters and code coverage.

This is a simple example of using the test runner:

import { function test(name?: string, fn?: test.TestFn): Promise<void> (+3 overloads)
The `test()` function is the value imported from the `test` module. Each invocation of this function results in reporting the test to the `TestsStream`. The `TestContext` object passed to the `fn` argument can be used to perform actions related to the current test. Examples include skipping the test, adding additional diagnostic information, or creating subtests. `test()` returns a `Promise` that fulfills once the test completes. if `test()` is called within a suite, it fulfills immediately. The return value can usually be discarded for top level tests. However, the return value from subtests should be used to prevent the parent test from finishing first and cancelling the subtest as shown in the following example. ```js test('top level test', async (t) => { // The setTimeout() in the following subtest would cause it to outlive its // parent test if 'await' is removed on the next line. Once the parent test // completes, it will cancel any outstanding subtests. await t.test('longer running subtest', async (t) => { return new Promise((resolve, reject) => { setTimeout(resolve, 1000); }); }); }); ``` The `timeout` option can be used to fail the test if it takes longer than `timeout` milliseconds to complete. However, it is not a reliable mechanism for canceling tests because a running test might block the application thread and thus prevent the scheduled cancellation.
@sincev18.0.0, v16.17.0@paramname The name of the test, which is displayed when reporting test results. Defaults to the `name` property of `fn`, or `'<anonymous>'` if `fn` does not have a name.@paramoptions Configuration options for the test.@paramfn The function under test. The first argument to this function is a {@link TestContext } object. If the test uses callbacks, the callback function is passed as the second argument.@returnFulfilled with `undefined` once the test completes, or immediately if the test runs within a suite.
test
, const mock: test.MockTrackermock } from 'node:test';
import function assert(value: unknown, message?: string | Error): asserts value
An alias of {@link ok } .
@sincev0.5.9@paramvalue The input that is checked for being truthy.
assert
from 'node:assert';
import module "node:fs"fs from 'node:fs'; const mock: test.MockTrackermock.test.MockTracker.method<typeof fs, "readFile", () => Promise<string>>(object: typeof fs, methodName: "readFile", implementation: () => Promise<string>, options?: test.MockFunctionOptions): test.Mock<...> (+3 overloads)
This function is used to create a mock on an existing object method. The following example demonstrates how a mock is created on an existing object method. ```js test('spies on an object method', (t) => { const number = { value: 5, subtract(a) { return this.value - a; }, }; t.mock.method(number, 'subtract'); assert.strictEqual(number.subtract.mock.calls.length, 0); assert.strictEqual(number.subtract(3), 2); assert.strictEqual(number.subtract.mock.calls.length, 1); const call = number.subtract.mock.calls[0]; assert.deepStrictEqual(call.arguments, [3]); assert.strictEqual(call.result, 2); assert.strictEqual(call.error, undefined); assert.strictEqual(call.target, undefined); assert.strictEqual(call.this, number); }); ```
@sincev19.1.0, v18.13.0@paramobject The object whose method is being mocked.@parammethodName The identifier of the method on `object` to mock. If `object[methodName]` is not a function, an error is thrown.@paramimplementation An optional function used as the mock implementation for `object[methodName]`.@paramoptions Optional configuration options for the mock method.@returnThe mocked method. The mocked method contains a special `mock` property, which is an instance of {@link MockFunctionContext}, and can be used for inspecting and changing the behavior of the mocked method.
method
(module "node:fs"fs, 'readFile', async () => 'Hello World');
function test(name?: string, fn?: test.TestFn): Promise<void> (+3 overloads)
The `test()` function is the value imported from the `test` module. Each invocation of this function results in reporting the test to the `TestsStream`. The `TestContext` object passed to the `fn` argument can be used to perform actions related to the current test. Examples include skipping the test, adding additional diagnostic information, or creating subtests. `test()` returns a `Promise` that fulfills once the test completes. if `test()` is called within a suite, it fulfills immediately. The return value can usually be discarded for top level tests. However, the return value from subtests should be used to prevent the parent test from finishing first and cancelling the subtest as shown in the following example. ```js test('top level test', async (t) => { // The setTimeout() in the following subtest would cause it to outlive its // parent test if 'await' is removed on the next line. Once the parent test // completes, it will cancel any outstanding subtests. await t.test('longer running subtest', async (t) => { return new Promise((resolve, reject) => { setTimeout(resolve, 1000); }); }); }); ``` The `timeout` option can be used to fail the test if it takes longer than `timeout` milliseconds to complete. However, it is not a reliable mechanism for canceling tests because a running test might block the application thread and thus prevent the scheduled cancellation.
@sincev18.0.0, v16.17.0@paramname The name of the test, which is displayed when reporting test results. Defaults to the `name` property of `fn`, or `'<anonymous>'` if `fn` does not have a name.@paramoptions Configuration options for the test.@paramfn The function under test. The first argument to this function is a {@link TestContext } object. If the test uses callbacks, the callback function is passed as the second argument.@returnFulfilled with `undefined` once the test completes, or immediately if the test runs within a suite.
test
('synchronous passing test', async t: test.TestContextt => {
// This test passes because it does not throw an exception. function assert(value: unknown, message?: string | Error): asserts value
An alias of {@link ok } .
@sincev0.5.9@paramvalue The input that is checked for being truthy.
assert
.function assert.strictEqual<"Hello World">(actual: unknown, expected: "Hello World", message?: string | Error): asserts actual is "Hello World"
Tests strict equality between the `actual` and `expected` parameters as determined by [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is). ```js import assert from 'node:assert/strict'; assert.strictEqual(1, 2); // AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: // // 1 !== 2 assert.strictEqual(1, 1); // OK assert.strictEqual('Hello foobar', 'Hello World!'); // AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal: // + actual - expected // // + 'Hello foobar' // - 'Hello World!' // ^ const apples = 1; const oranges = 2; assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`); // AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2 assert.strictEqual(1, '1', new TypeError('Inputs are not identical')); // TypeError: Inputs are not identical ``` If the values are not strictly equal, an `AssertionError` is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is undefined, a default error message is assigned. If the `message` parameter is an instance of an `Error` then it will be thrown instead of the `AssertionError`.
@sincev0.1.21
strictEqual
(await module "node:fs"fs.
function readFile(path: fs.PathOrFileDescriptor, options: ({
    encoding?: null | undefined;
    flag?: string | undefined;
} & EventEmitter<T extends EventMap<T> = DefaultEventMap>.Abortable) | undefined | null, callback: (err: NodeJS.ErrnoException | null, data: NonSharedBuffer) => void): void (+3 overloads)
Asynchronously reads the entire contents of a file. ```js import { readFile } from 'node:fs'; readFile('/etc/passwd', (err, data) => { if (err) throw err; console.log(data); }); ``` The callback is passed two arguments `(err, data)`, where `data` is the contents of the file. If no encoding is specified, then the raw buffer is returned. If `options` is a string, then it specifies the encoding: ```js import { readFile } from 'node:fs'; readFile('/etc/passwd', 'utf8', callback); ``` When the path is a directory, the behavior of `fs.readFile()` and {@link readFileSync } is platform-specific. On macOS, Linux, and Windows, an error will be returned. On FreeBSD, a representation of the directory's contents will be returned. ```js import { readFile } from 'node:fs'; // macOS, Linux, and Windows readFile('<directory>', (err, data) => { // => [Error: EISDIR: illegal operation on a directory, read <directory>] }); // FreeBSD readFile('<directory>', (err, data) => { // => null, <data> }); ``` It is possible to abort an ongoing request using an `AbortSignal`. If a request is aborted the callback is called with an `AbortError`: ```js import { readFile } from 'node:fs'; const controller = new AbortController(); const signal = controller.signal; readFile(fileInfo[0].name, { signal }, (err, buf) => { // ... }); // When you want to abort the request controller.abort(); ``` The `fs.readFile()` function buffers the entire file. To minimize memory costs, when possible prefer streaming via `fs.createReadStream()`. Aborting an ongoing request does not abort individual operating system requests but rather the internal buffering `fs.readFile` performs.
@sincev0.1.29@parampath filename or file descriptor
readFile
('a.txt'), 'Hello World');
});

Contributed by Colin Ihrig in #46983

Performance

With the newly formed Node.js Performance team, there has been a renewed focus on performance since the last Major release. Node.js 20 includes many improvements to the fundamental parts of the runtime including URL, fetch(), and EventTarget.

The cost of initializing EventTarget has been cut by half, providing faster access to all subsystems using it. Additionally, V8 Fast API calls have been leveraged to improve performance in APIs such as URL.canParse() and timers.

Node.js 20 includes specific changes, such as the updated version 2.0 of Ada, a fast and spec-compliant URL parser written in C++.

Looking forward to new ways to improve performance we are currently working on reducing the cost of being specification compliant by refactoring to get rid of brand validations checks on streams, URL, URLSearchParams, and String Decoder. This helps support our general goal of being specification compliant where it makes sense.

If you have a passion for performance and Node.js, we are actively looking for contributors for our performance team.

Preparing single executable apps now requires injecting a Blob

The project has been working on support for Single Executable Applications (SEA) over the last year with initial support landing recently. The team continues to refine the approach as the feature is still Experimental. In Node.js 20, building a single executable app now requires injecting a blob prepared by Node.js from a JSON config instead of injecting the raw JS file.

Example:

sea-config.json

{
  "main": "hello.js",
  "output": "sea-prep.blob"
}

This writes the blob to the sea-prep.blob file.

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

This blob can now be injected into the binary.

This change was made to allow the the possibility of embedding multiple co-existing resources into the SEA (Single Executable Apps) which opens up new use cases.

Contributed by Joyee Cheung in #47125

Web Crypto API

The project works toward interoperability with other JavaScript environments. As an example of that in Node.js 20, the Web Crypto API functions' arguments are now coerced and validated as per their WebIDL definitions like in other Web Crypto API implementations. This further improves interoperability with other implementations of Web Crypto API.

This change was made by Filip Skokan in #46067.

Official support for ARM64 Windows

Node.js has broad platform and architecture support and people seem to want it to run everywhere. We are happy to share that Node.js now includes binaries for ARM64 Windows, allowing for native execution on the platform. The MSI, zip/7z packages, and executable are available from the Node.js download site along with all other platforms. The CI system was updated and all changes are now fully tested on ARM64 Windows, to prevent regressions and ensure compatibility.

ARM64 Windows was upgraded to tier 2 support by Stefan Stojanovic in #47233.

Progress on Web Assembly System Interface (WASI)

The project continues to work on the WASI implementation within Node.js. Some notable progress is that although it is experimental a command line option is no longer required to enable WASI. This should make it easier to consume. As the team working on WASI looks forward to preview2 a few changes were also made to plan for the future. That included adding a version option when new WASI() is called. In the 20.x release the version is required and has no default value. This is important to that as new versions are supported applications don't default to what may be an obsolete version. This does mean, however, that any code that relied on the default for the version will need to be updated to request a specific version.

If you have a interest in using WASI in Node.js or uvwasi which is used outside of Node.js itself, the team would love additional contributors.

Call to action!

Try out the new Node.js 20 release! We’re always happy to hear your feedback. Testing your applications and modules with Node.js 20 helps to ensure the future compatibility of your project with the latest Node.js changes and features.

Also of note is that Node.js 14 will go End-of-Life in April 2023, so we advise you to start planning to upgrade to Node.js 18 (LTS) or Node.js 20 (soon to be LTS).

Please, consider that Node.js 16 (LTS) will go End-of-Life in September 2023, which was brought forward from April 2024 to coincide with the end of support of OpenSSL 1.1.1. You can read more details about that decision at https://nodejs.org/blog/announcements/nodejs16-eol/.

Looking to the future, the Next-10 team is running a survey to gather info from the ecosystem. Help shape the future of Node.js by participating. Submit your feedback here.