18 April 2023
New Node.js features bring watch mode and stable Web Crypto API. Check out the updated Node version report
Known for its speed, simplicity, and stability, Node.js does not introduce too many novelties. Even if it does, they typically enter the release schedule as experimental features. However, the combined contribution of versions 16 through 20 gives us enough new upgrades to write and get excited about. Learn how you can use and benefit from these features in your projects.
When it comes to new Node.js features, the OpenJS Foundation, which oversees the development of Node.js, opts for relatively frequent, yet modest releases. Last year brought two versions and the latest one Node.js 20 was released on April 18 this year.
Below, you will find an overview of what I consider the most important changes introduced in the latest Node versions, starting with the one that came last – version 20 – all the way to version 10.
Before we get to that, let’s take a look at the Node.js release schedule.
Also, keep in mind, the article also includes archives of older versions of Node.js. Use the left-hand sidebar to scroll to the specific version you are interested in. The latest covered version is 20.
Node.js releases explained
Including the latest one, there are 18 Node.js versions. Each one has a release status assigned to it. The most important types include:
- Current – since new versions are typically released every 6 months, each new version enters the Current status for half a year from the day of its release. After this period, odd-numbered releases become unsupported.
- Active LTS – even-numbered releases instead move to the Active LTS status. They are now ready for general use. You can expect support, including critical bug fixes, for the duration of 30 months.
- Maintenance LTS – only one even-numbered version at a time carries the Active LTS status. Once a new one shows up and completes the Current status, the previous one moves to the Maintenance LTS. It will continue to receive updates until its end-of-life date.
What about the status of the latest Node.js releases?
Node.js 16 is now the Active LTS version. The odd-numbered version 17 has just lost its Current status and its end-of-life is coming this June. Node.js 18 is now the Current version and is expected to become the new Active LTS version this October.
Let’s get to the overview of features!
The Node.js version 20 came out on April 18. Since this is an even-numbered release, it’s going to receive long-term support and will enter active LTS status in October.
Node.js 20 is still only being rolled out but you can learn a lot about what’s new and improved in this version. To sum things up:
- The Permission Model is a new experimental feature that makes it possible to restrict access to specific resources during program execution, including file system operations. It’s going to come in handy for protecting sensitive information. It’s behind a flag –experimental-permission.
- ESM hooks supplied via loaders are now on a dedicated thread.
- The V8 engine is now raised to version 11.3, part of Chromium 113.
- The Test Runner is now stable (!).
- The latest version of the Ada URL parser is added.
- Support for ARM64 Windows.
- New tweaks and improvements for Web Crypto API.
We’re going to expand the section as we continue to experiment and learn more about Node.js 20!
Node.js 19 was only released back in October 2022, but as an odd-numbered version it is already on the verge of becoming unsupported. Still, it offers some very interesting new features. Here are our favorites.
node –watch (experimental)
The newly supported watch mode restarts a process at a time when a change occurs to an imported file. You can turn it on with the following command:
$ node --watch index.js
The Software House’s senior Node.js developer Rafał Gołubowicz is very fond of this addition:
Most Node.js developers got used to installing dev dependencies that allowed them to auto-restart the server when any changes in the code appeared. Nodemon is one such solution. The new –watch flag is something that the Node.js community has been talking about and advocating for years. It allows for getting rid of external dependencies to live-reload. That’s a great adjustment.
HTTP(S)/1.1 KeepAlive by default
The Keep-Alive instruction allows a single TCP connection to stay open for a multitude of HTTP requests. Starting with Node.js 19, it’s going to be true by default. Any outgoing HTTP(S) connection will use it. The default duration is 5 seconds.
Should the feature become stable, it will definitely improve throughput as all the available connections will now be reused by default.
Stable Web Crypto API
Node.js 19 brings a stable version of the WebCrypto API. The globalThis.crypto class accesses the module that provides a variety of low-level cryptographic functions.
Hopefully, this change will make the Web Crypto API more popular than it has been so far within the Node.js community.
What kind of projects did we develop with Node.js?
Check out the portfolio and take a look at our Node.js-related success stories.
Node.js version 18 & earlier
Versions 16-18 brought a lot of interesting developments. Let’s start with version 18, which as of March 27 2023 is still the active LTS version.
New globally available browser-compatible APIs
The node-fetch module has always been very helpful as an easy way to fetch data to and from external web servers or APIs. Now, using it will become even more efficient. Node 18 features an experimental global fetch API, which enables the same methods without having to rely on external packages.
The new implementation comes from undici and is inspired by node-fetch. The implementation tries to be as specification-compliant as possible, making the native browser fetch API compatible with NodeJS. With that change, the browser globals fetch, FormData, Headers, Request, and Response are now available in Node.js.
Here’s how the new API works:
The new feature will certainly make the Node index more powerful.
Web Streams API
This API is all about streaming — that is — breaking a resource into small chunks and processing it little by little. The technique has its benefits in terms of both performance and user experience on the web. The Web Streams API includes a number of stream interfaces that work alone or in concert to process various types of resources.
With Node.js 18, the experimental implementation of Web Stream is exposed to the global scope, making all the various stream interfaces globally accessible.
There are some other APIs that received significant attention in the latest Node.js release. Both Blob and BroadcastChannel are no longer experimental. In addition, they are now available globally. Blob makes it easier to share data across worker threads, while BroadcastChannel simplifies certain aspects of asynchronous communication.
Test runner module (experimental)
The Node.js team is actively trying to introduce new out-of-the-box features that can replace various third-party modules and frameworks. When done well, such changes can:
- decrease the complexity of an environment,
- improve CI/CD workflows,
- remove maintenance overhead.
Here is an example of how you can use the new test runner in your project:
OpenSSL 3.0 support
OpenSSL is a popular library used to secure communications over computer networks. It was originally written in C, but developers can use wrappers in order to benefit from this library in a variety of programming languages and environments, including Node.js.
Starting with version 17, Node.js developers can now use OpenSSL 3.0. The most notable improvement in the new OpenSSL version is support for QUIC – a transport layer network protocol designed by Google to improve internet/network latency.
Stable timers promises API
A new stable version of the timers promises API has been made available starting with Node.js 16. It was originally introduced as an experimental feature in the previous release.
Promises-based timer functions are handy for a lot of things. You can use them to make your Node.js scripts wait for other tasks/systems to finish their job. It was possible before, but with the new API, it takes much less code to achieve.
The timers promises API includes a total of 3 minor APIs:
- setImmediate fulfills a promise immediately.
- setTimeout delays a number of milliseconds before it moves to fulfill the promise.
- setInterval returns an async iterator. It generates values in an interval of delayed milliseconds.
Changes to the toolchain & compiler
Node.js provides pre-built binaries for a variety of platforms. They make it easier to quickly set up your environment. Node.js 16 brought a couple of new ones:
- The macOS installer ‘fat’ (multi-architecture) binary.
- Individual tarballs for Intel (darwin-x64) and ARM (darwin-arm64).
- The prebuilt binaries for Apple Silicon.
Let’s take a look at one more thing that concerns all of the releases in this section.
- Faster super property access mainly by applying an inline cache system.
- New RegExp features from ECMAScript 2022, including match indices.
- Upgrades for the Intl.DisplayNames, Intl.DateTimeFormat, and Intl.Locale APIs, in particular, you should check out the new timeZoneName options.
- Improved performance of class fields and private class methods (faster initialization).
- New array methods: findLast() and findLastIndex().
That’s it for the 16-18 Node.js update!
Node.js version 15 & earlier
NPM 7 – what’s new?
Node.js was not a major release, but it is still very interesting. With Node 15 , we’re getting npm 7.
Workspaces are one of the most interesting ones. Yep, npm is getting what yarn has been known for. If you’re using lerna or was thinking about monorepo for your packages, this is what you were looking for. Read more about this on the npm repository page.
The second one is also somehow related to yarn. With npm 7, the yarn.lock file is no longer ignored. Npm is going to read yarn lock for package metadata and resolution guidance and then create its own package-lock.json.
Package-lock has also changed. With npm 7, we are introduced to a completely new format of that file. The new version (V2) contains everything that is necessary for deterministically reproducible builds. This is the difference between npm and yarn. Yarn deterministic builds are based on a yarn.lock and a version of yarn. So if we used a different version, then we’re going to get a different set of packages.
For npm, this is going to be consistent even between different versions.
Unhandled rejections change
For typical synchronous flow, Node.js closes its process as soon as error code is thrown. However, it’s a different story when it comes to asynchronous operations.
For years, we had to add two important event handlers to secure our apps – uncaughtException and unhandledRejection.
Node 15 introduces a huge update on how unhandled promise rejections are handled. The default mode was changed from warn to throw.
In Node 14, an unhandled rejection printed a warning to the console. In Node 15, those will be handled as uncaught exceptions and will cause your app to exit.
This is a good change!
V8 new version
As usual, a new Node is shipped with the new version of V8. This time, we’re getting four new features:
The first one is support for Promise.any. This is something similar to Promise.race. However, the resolution is a bit different. Promise.any is resolved as soon as at least one promise is fulfilled and rejected only when all promises are rejected – learn more.
The next one is related to error aggregation. With V8 8.6, we’re getting a new error type – AggregateError. It takes an array of errors. A good example is an error from Promise.any. Instead of throwing an array of errors, it throws a single one containing an array of errors.
The third one is a support for String.prototype.replaceAll. As the name says, it is about replacing all occurrences of a specific string or pattern. For example:
The last one are new logical operators – &&=, ||=, ??=. It is easier to see them in action than to explain it, so let’s see the code:
It all comes on top of the changes and improvements introduced back in Node 14 with V8 8.1:
- Access to the private field.
- Awaits should work much faster, as should JS parsing.
- Our apps should load quicker and asyncs should be much easier to debug, because we’re finally getting stack traces for them.
- The heap size is getting changed. Until now, it was either 700MB (for 32bit systems) or 1400MB (for 64bit). With new changes, it’s based on the available memory!
If you’re using TypeScript, you probably work with nullish coalescing and optional chaining. Both of those are natively starting with Node 14.
We’re also getting a few updates to Intl. The first one is support for Intl.DisplayNames and the second one is support for calendar and numberingSystem for Intl.DateTimeFormat.
New experimental features
Node 15 also brings some new experimental features.
The first one is a support for QUIC – a new UDP-based transport protocol that is used in HTTP/3. You can find more information about this here.
If you want to play with it, remember to run node with –experimental-quic.
The second one is AbortController. The main use case is signalling the promise cancellation. Right now the implementation is experimental, but still, we’re waiting for more!
Node + Web Assembly = <3
Web Assembly is slowly gaining in popularity. More and more Node modules are experimenting with this language and so is Node itself! With the Node 15 release, we gain access to the experimental Web Assembly System Interface – WASI.
The main idea behind this is to provide us with an easier way to access the underlying operating system. I’m really looking forward to seeing more Web Assembly in Node.
* Below, you will find information on Node.js ver. 12 and earlier. *
How to use TypeScript with Node.js efficiently? Find out from this article.
Need support with top-class Node.js programming?
🚏 Find out what you can achieve with Poland’s biggest Node team that builds performance-driven apps for millions of users.
Node.js version 12 & earlier
Stable Diagnostic Reports
In Node 12, we’ve got a new experimental feature called Diagnostic Reports. By using it, we could easily get a report that contains information about the current system. What’s more, we can generate it not only on demand but also after a certain event.
If you have any production running a Node app, then this is something you should be checking out.
Threads are almost stable!
With the last LTS we’ve got access to threads. Of course, it was an experimental feature and required a special flag called –experimental-worker for it to work.
With Node 12 it’s still experimental, but won’t require a flag anymore. We are getting closer to a stable version!
ES modules support
Of course, we could use Babel or Typescript, but since Node.js is a backend technology, the only thing we should care about is the version of the Node installation on the server. We don’t need to care about multiple different browsers and support for them, so what’s the point of installing a tool that was made precisely with that in mind (Babel/Webpack etc.)?
With Node 12, it’s getting a little bit easier to work with. Much like it is with web apps, we get a special property type called that will define if code should be treated like common.js or es module.
The only thing you need to do to treat all your files as a module is to add the property type with the value module to your package.json.
From now on, if this package.json is the closest to our .js file, it will be treated like a module. No more mjs (we can still use it if we want to)!
So, what if we wanted to use some common.js code?
As long as the closest package.json does not contain a module type property, it will be treated like common.js code.
What’s more, we are getting new an extension called cjs – a common.js file.
Every mjs file is treated as a module and every cjs as a common.js file.
If you didn’t have a chance to try it out, now is the time!
JS and private variables
JS is famous for its monkey patching, meaning we could always somehow access almost everything.
We tried with closures, symbols and more to simulate private-like variables. Node 12 ships with the new V8 and so we’ve got access to one cool feature – private properties in the class.
I’m sure you all remember the old approach to privates in Node:
We all know it’s not really a private – we are still able to access it anyway, but most of IDEs treated it like a private field and most of Node devs knew about this convention. Finally, we can all forget about it.
Can you see the difference? Yes, we use # character to tell Node that this variable is private and we want it to be accessible only from the inside of this class.
Try to access it directly, you’ll get an error that this variable does not exists.
Sadly some IDE do not recognize them as proper variables yet.
Flat and flatMap
First of all, we’re getting access to new array methods – flat and flatMap. The first one is similar to Lodash’s flattenDepth method.
If we pass a nested arrays to it, we will get a flatten array as a result.
As you can see, it also has a special parameter – depth. By using it, you can decide how many levels down you want to flatten.
The second one – flatMap – works just like map, followed by flat 🙂
Optional catch binding
Another new feature is optional catch binding. Until now we always had to define an error variable for try – catch.
With Node 12 we can’t skip the entire catch clause, but we can skip the variable at least.
The new Node.js is all about threads!
If there is one thing we can all agree on, it’s that every programming language has its pros and cons. Most popular technologies have found their own niche in the world of technology. Node.js is no exception.
We’ve been told for years that Node.js is good for API gateways and real-time dashboards (e.g. with websockets). As a matter of fact, its design itself forced us to depend on the microservice architecture to overcome some of its common obstacles.
At the end of the day, we knew that Node.js was simply not meant for time-consuming, CPU-heavy computation or blocking operations due to its single-threaded design. This is the nature of the event loop itself.
If we block the loop with a complex synchronous operation, it won’t be able to do anything until it’s done. That’s the very reason we use async so heavily or move time-consuming logic to a separate microservice.
This workaround may no longer be necessary thanks to new Node.js features that debuted in its 10 version. The tool that will make the difference are worker threads. Finally, Node.js will be able to excel in fields where normally we would use a different language.
A good example could be AI, machine learning or big data processing. Previously, all of those required CPU-heavy computation, which left us no choice, but to build another service or pick a better-suited language. No more.
Threads!? But how?
This new Node.js feature is still experimental – it’s not meant to be used in a production environment just yet. Still, we are free to play with it. So where do we start?
Starting from Node 12+ we no longer need to use special feature flag –experimental-worker. Workers are on by default!
Now we can take full advantage of the worker_threads module. Let’s start with a simple HTTP server with two methods:
- GET /hello (returning JSON object with “Hello World” message),
- GET /compute (loading a big JSON file multiple times using a synchronous method).
The results are easy to predict. When GET /compute and /hello are called simultaneously, we have to wait for the compute path to finish before we can get a response from our hello path. The Event loop is blocked until file loading is done.
Let’s fix it with threads!
As you can see, the syntax is very similar to what we know from Node.js scaling with Cluster. But the interesting part begins here.
Try to call both paths at the same time. Noticed something? Indeed, the event loop is no longer blocked so we can call /hello during file loading.
Now, this is something we have all been waiting for! All that’s left is to wait for a stable API.
Want even more new Node.js features? Here is an N-API for building C/C++ modules!
The raw speed of Node.js is one of the reason we choose this technology. Worker threads are the next step to improve it. But is it really enough?
Node.js 10 gives us a stable N-API. It’s a standardized API for native modules, making it possible to build modules in C/C++ or even Rust. Sounds cool, doesn’t it?
A very simple native module can look like this:
If you have a basic knowledge of C++, it’s not too hard to write a custom module. The only thing you need to remember is to convert C++ types to Node.js at the end of your module.
Next thing we need is binding:
This simple configuration allows us to build *.cpp files, so we can later use them in Node.js apps.
Once the module is good to go, we can use the node-gyp rebuild command to build and then require it in our code. Just like any popular module we use!
Together with worker threads, N-API gives us a pretty good set of tools to build high-performance apps. Forget APIs or dashboards – even complex data processing or machine learning systems are far from impossible. Awesome!
Full support for HTTP/2 in Node.js? Sure, why not!
We’re able to compute faster. We’re able to compute in parallel. So how about assets and pages serving?
For years, we were stuck with the good old http module and HTTP/1.1. As more and more assets are being served by our servers, we increasingly struggle with loading times. Every browser has a maximum number of simultaneous persistent connections per server/proxy, especially for HTTP/1.1. With HTTP/2 support, we can finally kiss this problem goodbye.
So where do we start? Do you remember this basic Node.js server example from every tutorial on web ever? Yep, this one:
With Node.js 10, we get a new http2 module allowing us to use HTTP/2.0! Finally!
What does the future hold for Node.js web development?
In Node.js 18, the team is trying to make the environment even more comprehensive, while keeping it reasonably lightweight. The best examples of that are the new fetch and test runner updates. They add out-of-the-box support for features that used to be only available through third-party tools, focusing only on their most essential, core functionalities. As a result, Node.js is getting increasingly robust, without becoming overly complex – a feat made possible partially due to its modular nature.
Node.js continues to grow. The available data shows that software companies are not going to get fed up with Node.js developers anytime soon – the usage share of Node.js across the web rose to almost 2 percent over the last year.
💡 Read more
If you made it all the way here, you must be really passionate about Node.js! So is The Software House. In fact, Node.js services are the biggest part of our technology stack.
If you are looking for Node.js job openings, check out our career page. And if you are searching for skilled Node.js experts to help you create an excellent product, contact us today and schedule free consultations.