IBM Developer Blog

Follow the latest happenings with IBM Developer and stay in the know.

The Node.js 14 release landed today. Learn about the new features and enhancements related to diagnostics, performance, and internationalization.


It’s April, which means it’s time for another Node.js release. Today, we’re releasing Node.js 14, with new features that are important to Node.js users, including IBM’s customers. While it won’t be promoted to long-term support (LTS) until October, we need our customers and the greater ecosystem to try it out and give us feedback. This will allow us to address any issues in advance and make sure both the release, the ecosystem, and our customers are ready when it’s promoted.

The 14.x release delivers key enhancements, including the addition of Diagnostic Reports as a stable feature, an experimental async local storage API to let you trace a transaction through different steps within a process and to external resources, support for internationalization, and easier native module use.

Watch a video of Bethany Griggs, Node.js release manager, talk with Michael Dawson, TSC Chair, about the new release:

IBM’s strategy with our Node.js involvement is to focus on aspects we believe are important to our customers, including stable and predictable releases, platform support, security, diagnostics, performance, code quality and safety nets, and key features. The 14.x release delivers in a number of those areas, and I’ll dive into some of the key highlights in the sections which follow.

Diagnostic Report added as a stable feature

Diagnostic Report is now a stable feature as of Node.js 14. This feature provides an easily consumable report with information that can be valuable in the initial diagnosis and triage of production issues. We have found this capability to be important for supporting our customers across languages, including Node.js, Java, and Swift. With Diagnostic Report built into Node.js as a stable feature, you can feel confident about using it in production and have it ready to quickly resolve your problems.

To read more, check out this article, Easily identify problems in your Node.js applications with Diagnostic Report from Node.js collaborator and IBMer Gireesh Punathil.

Diagnostic Report provides a JSON-based report which is both machine and human readable. It provides valuable information that may help you quickly diagnose problems. For example, imagine your application is crashing intermittently and core files are not being generated. You can generate a Diagnostic Report and check the user limits to make sure they are configured to allow core file generation. In this example you can see that the ulimit is set such that you won’t get core files (limit is set to 0).

    "userLimits": {
    "core_file_size_blocks": {
      "soft": 0,
      "hard": "unlimited"
    },

Similarly, if your application is running out of memory, you can check if the maximum heap size has been limited through the max-old-space-size command-line arguments:

 "commandLine": [
      "node",
      "--max-old-space-size=128",
      "testd

You can configure a Diagnostic Report to be generated on certain events like receiving a signal, an uncaught exception, or on a fatal error. You can also generate one in your application with a few lines of code:

const process = require('process');
process.report.writeReport();

If you are running from the command line, this information may already be easily available. However, with many of today’s production deployments being in containers which are managed and configured outside of your control, Diagnostic Report can often provide valuable insight into how an external configuration which may be impacting how your applications runs.

If you are not already using Diagnostic Report, I recommend that you read up on it and think about how you can use it in production. You can read more about the feature in the API docs here: https://nodejs.org/api/report.html.

Making Diagnostic Report easier to consume

Experience has shown that it is often necessary to look at several diagnostic reports in sequence or to look for one of many different possible problems within a Diagnostic Report. report-toolkit is a tool developed by IBM to improve working with Diagnostic Reports.

Although report-toolkit is not part of the Node.js 14 release, as Diagnostic Report is now a stable feature, we want to take this opportunity to make people aware of report-toolkit and to ask you to try it out and give us your feedback.

report-toolkit incorporates a number of key features:

  • built-in inspect rules that look for common problems
  • redaction of secrets
  • purpose-built diffs between two or more Diagnostic Reports
  • transformers to extract and format key information

You can read more about report-toolkit in the article, Introducing report toolkit for nodejs diagnostic reports, by IBMer Christopher Hiller. I think you’ll find the combination of Diagnostic Report and report-toolkit can help make investigating problems and sharing information easier.

Experimental async local storage API

Being able to trace a transaction through different steps both within a process as well as to external resources (for example, a database call) is an important enterprise requirement. This capability often provides the information you need to identify where in your application a problem is occurring or to identify performance bottlenecks.

The asynchronous nature of Node.js means that solutions from other languages (such as Thread Local Storage) don’t work for Node.js. The community has been working on APIs to help track and manage context through asynchronous calls over a number of releases. The experimental Async Hooks API was added to help address this problem, but is still working to become stable.

Node.js 14 adds a new Experimental API called AsyncLocalStorage. The hope is that this higher level API may have an easier time getting to stable as it exposes fewer internals and provides a simpler API. While a number of npm modules provided similar functionality, the difficulties in maintaining and getting packages to use them in a comprehensive way has resulted in consensus that an API should be provided as part of Node.js itself. I’m happy see this key capability making it into core.

Let’s take a look at the API in action:

const AsyncLocalStorage = require('async_hooks').AsyncLocalStorage;
const asyncLocal1 = new AsyncLocalStorage();

function logData() {
  console.log(asyncLocal1.getStore(['url']));
}

function accept(request, response) {
  asyncLocal1.getStore()['url'] = request.url;
  setTimeout(logData, 100);
};

asyncLocal1.run(new Object(), accept.bind(null, { url: 'url1'}, {}));
setTimeout(() => { asyncLocal1.run(new Object(), accept.bind(null, { url: 'url2'}, {})) },
  500);

This example tries to emulate the case of a web server that may have multiple active asynchronous flows at the same time — one for each connection. It is not a complete example as it tries to limit the code to the number of lines needed to illustrate how the API works.

We start out by getting a reference to the API and creating an instance of AsyncLocalStorage. You’ll note that the Async Local Storage API is part of the top level async_hooks namespace. One important aspect is that this object needs to be available, based on JavaScript scoping rules, to all code that needs to access the context for an asynchronous flow.

The accept function is the function that the server would call for each connection request. For the purposes of the example, it simply sets a value in the store returned by asyncLocal1.getStore() and then makes an asynchronous call by using SetTimeout.

When the logData function is invoked, it pulls the value stored in the AsyncLocalStorage by the accept function and then logs it to standard out.

The last few lines show running the accept call using asyncLocal1.run(). The storage for an asynchronous flow is propagated only to those functions within the scope of that run method. The API provides additional methods that you can use to stop propagation of the storage within the flow if needed.

Now, your first question might be “Can’t I just do the same thing by using a global object like let url; and write/read from that instead?” While that would work if you only have one flow active at a given time, the problem is that if you have multiple flows, they will overwrite each other’s values. The key thing to realize is that. even though all flows use the same global asyncLocal1 object, when getStore() is called within an asynchronous flow, it gets an object which is unique to that flow.

For example, in the two flows within the example:

asyncLocal1.run(new Object(), accept.bind(null, { url: 'url1'}, {}));
  --> asyncLocal1.getStore()
    --> store1
setTimeout(() => { asyncLocal1.run(new Object(), accept.bind(null, { url: 'url2'}, {})) }, 500);
  --> asyncLocal1.getStore()
    --> store2

I hope this gives you the flavor of the API and piques your interest, so you’ll try it out. While it was built using what’s been learned by the previous npm packages, we need your help making sure it covers all of the required use cases and is easy to use correctly.

Internationalization support

Internationalization is a key requirement both within IBM and for our customers. While not a new feature in version 14, when it is promoted to LTS in October Node.js 14 will be the first LTS release to include full ICU data by default. You can read more about the feature in the Node.js 13 release blog.

For example, one of our large Node.js deployments powering weather.com supports 230+ locales and is localized in about 60 languages. Read more about how Node.js powers weather.com.

Making native modules easier

Node.js 14 makes it easier to create, build, and support native modules (also known as addons). IBM is one of the ongoing contributors to N-API, and Node 14 comes with N-API version 6, which includes support for BigInt along with other improvements. For more information about N-API, check out the API docs and node-addon-api.

Thanks to our great team

In closing, I’d like to thank the release team including release manager and IBMer Bethany Griggs for all of their hard work in getting Node.js 14 out. I’d also like to thank the supporting cast from the build working group and, of course, the individual contributors as well.

How you can contribute to Node.js

Committing code and participating in the work of the Node.js organization is not the only way to contribute to the continued success of Node.js. As a Node.js package developer or end user, I ask that you help out by testing early and often and provide feedback to the Node.js project.

Our regular, 6-month (April and October) major release cycle provides a good opportunity to test out releases in advance so that when a release is promoted to LTS, there are no surprises. We need your help in doing that testing. Once a release is in LTS, consider testing your applications and packages regularly on the LTS versions in order to ensure a good experience for end users who are migrating to those versions. If you find issues please report them by opening an issue in the Node.js repository.

If you are a package maintainer, please consider aligning your releases and support cycles with the Node.js release lines. This article from the OpenJS Foundation makes a great case for why this benefits the ecosystem.

Learn more

If you want to read more about this release, check out the community blog post announcing the Node.js 14 release.

You can also: