Node.js 21 has been just released. You can learn more about the release schedule here. This new version comes with lots of internal improvements and a few user-facing new features, which we’re going to cover in this article. A notable dependency update is the V8 update to v11.8. All the notable changes about V8 can be found in the roadmap.
Experimental support for WebSocket API
This new feature introduced by this Node.js release brings experimental support for WebSocket. Thanks to this change, you can now connect to a WebSocket server from a Node.js application, without resorting to third-party packages.
This addition is significant as it natively integrates WebSocket capabilities into Node.js, enabling real-time, two-way interactive communication between client and server. It can be beneficial for developing real-time applications like chat applications, live updates on websites, and interactive games.
Here’s an example of how you can use it:
Copy to Clipboard
If you save this code in a file named example.js and run it with node --experimental-websocket example.js, you’ll connect to a WebSocket server, send messages to it and receive back messages.
The new --experimental-default-type flag in Node.js allows you to set how files are interpreted: as “commonjs” or as “module”. Here’s what happens when you use it set to module:
Any code you directly input or send to Node.js will be seen as an ES module unless you say otherwise.
If there’s no package.json in the folder (or in any parent folder), then .js files or files without an extension are seen as ES modules.
If there is a package.json but it doesn’t say what type of module to use, and the folder isn’t inside node_modules, then .js files or files without extensions are seen as ES modules.
If you use the --experimental-wasm-modules flag, any file without an extension containing a specific Wasm header will be seen as a Wasm module.
The new ES module way of loading is used right from the start. You can’t modify how Node.js starts modules using --require anymore.
To summarize, this flag facilitates a more flexible module system handling in Node.js, allowing for easier adoption of ES modules and WebAssembly, while maintaining backwards compatibility with CommonJS.
Test runner concurrency flag
After the introduction of the new built-in test runner accessible via node:test, a lot of effort has been made to improve it. In Node.js v21 a --test-concurrency flag was introduced.
With this flag, the user can set the maximum number of test files that the test runner will execute concurrently. The default value is set to one less than the total number of parallel processing units available.
Test files are run in separate processes. The maximum number of these processes running at once is determined by the --test-concurrency flag. If a process ends with a 0 exit code, the test passes. If not, it fails. These test files should be executable by Node.js, but they don’t need to use the node:test module.
This is aimed at enhancing the flexibility and efficiency of running tests in parallel, which can significantly speed up the testing process, especially in large codebases or on systems with multiple cores.
Test runner now supports passing globs
By default, during testing, Node.js will decide automatically which files to run based on some patterns like */*.test.?(c|m)js.
From v21, the user will be able to specify one or more glob patterns as final argument(s) in the command. Here an example of the usage: node --test **/*.test.js **/*.spec.js. The main benefits of this approach are more flexibility and control from the end user. By allowing custom glob patterns, Node.js offers users the ability to tailor their testing process to their specific needs and project structures.
Update on llhttp Version 9.1.2
The latest update introduces the option to enable more leniency flags on the HTTP parser. This means that llhttp can now interact with additional non-standard HTTP implementations more seamlessly. However, enabling these leniency flags exposes applications to potential request smuggling or poisoning attacks. Therefore, it is highly recommended to refrain from using this option unless there is a compelling reason to do so.
The flag to use in order to enable leniency flags is: --insecure-http-parser.
Differences from previous versions
For those familiar with llhttp versions before 9, it’s worth knowing how the leniency flags have evolved:
In versions before llhttp 8, the parser had a strict mode, and leniency flags were separate. Both of these were disabled by default in the code.
With the introduction of llhttp 9, there is no concept of strict mode anymore and the rules previously enforced by it are enabled by default and can be disabled via leniency flags. This means there’s no halfway mark; developers have to choose between strict parsing and full leniency.
Add flush option to writeFile() functions
This change, introduces a flush option to the fs.writeFile family of functions in Node.js. When the flush option is used, data is forced to be flushed to permanent storage at the end of a successful write operation, preventing subsequent read operations from encountering stale data.
Partial implementation of the Navigator API
The Navigator is widely used in frontend contexts. In Node.js v21 navigator.hardwareConcurrency is available. Probably, other Navigator APIs will be released in the next versions.
Reduce parts in chunked response when corking
This change aim to enhance performance by preventing the splitting of chunked responses into multiple parts when corking. This change avoids creating a separate chunk for each call to .write(...) when the response is corked, reducing overhead on both client and server sides. When uncorking the response, a single chunk is created for all calls to write(...), improving the efficiency of handling chunked responses in Node.js
What else is there?
Node.js 21 comes with a long list of other improvements, although most of them are not immediately relevant to end users. Check them out in the official release notes!