News Blog

Stay tuned as the company news with our expert team innovations in latest technology.

What’s new in Node.js 19


Node.js major release is rolled out every six months. The new release becomes the Current release for six months, which gives library authors time to add support for them.

After six months, odd-numbered releases, such as 19, become unsupported, and even-numbered releases, such as 18, move to the Active LTS (long-term support) status and are ready for general use.

LTS release typically guarantees that critical bugs will be fixed for a total of 30 months. Production applications should only use Active LTS or Maintenance LTS releases.

Node.js 19 was released recently which comes with 6 major features:

  • Experimental node watch mode
  • HTTP(S)/1.1 KeepAlive by default
  • Stable WebCrypto
  • Custom ESM resolution adjustments
  • Dropped DTrace/SystemTap/ETW support
  • V8 JavaScript engine is updated to V8 10.7

Let’s explore what they are and how to use them.

Use NVM to explore node

Run the command to install node 19.0.0:

% nvm install 19.0.0
Downloading and installing node v19.0.0...

Computing checksum with sha256sum
Checksums matched!
Now using node v19.0.0 (npm v8.19.2)

On any window, run the command to use node 19:

% nvm use 19
Now using node v19.0.0 (npm v8.19.2)

Now you’re ready to explore:

% node --version

Experimental node watch mode

In A Hands-on Guide for a Server-Side Rendering React 18 App, you have to build a production Create React App by executing npm run build.

You can created server/index.js:

const express = require("express");
const path = require("path");
const app = express();
app.use(express.static(path.join(__dirname, "../build")));

app.listen(8080, () =>
console.log("Express server is running on localhost:8080")

The server was running with nodemon, a tool that helps to develop Node.js applications by automatically restarting the application when file changes are detected. The command is nodemon server.

With node.js 19, you no longer need to install the additional tool. Instead, you can execute node –watch to automatically restart the application when file changes are detected.

% node --watch server
(node:67643) ExperimentalWarning: Watch mode is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Express server is running on localhost:8080

Stable WebCrypto

The WebCrypto API is an interface to build systems using cryptography. With node.js 19, the WebCrypto API is stable (with the exception of these algorithms: Ed25519, Ed448, X25519, and X448).

You can use globalThis.crypto or require(‘node:crypto’).webcrypto to access this module. The following server/index.js use subtle as an example, where the SubtleCrypto interface provides a number of low-level cryptographic functions:

const { subtle } = globalThis.crypto;

(async function() {

const key = await subtle.generateKey({
name: 'HMAC',
hash: 'SHA-256',
length: 256
}, true, ['sign', 'verify']);

console.log('key =', key);

const enc = new TextEncoder();
const message = enc.encode('I love cupcakes');

console.log('message =', message);

const digest = await subtle.sign({
name: 'HMAC'
}, key, message);

console.log('digest =', digest);

  • An HMAC key is generated. HMAC is a specific type message authentication code (MAC) that involves a cryptographic hash function and a secret cryptographic key. The generated key can be used to simultaneously verify the data integrity and authenticity of a message.
  • A message, I love cupcakes, is encoded.
  • A message digest is created with key and message. A message digest is a cryptographic hash function that contains a string of digits created by a one-way hashing formula.

The following console information shows the values of key, message, and digest:

% node server
key = CryptoKey {
type: 'secret',
extractable: true,
algorithm: { name: 'HMAC', length: 256, hash: [Object] },
usages: [ 'sign', 'verify' ]
message = Uint8Array(15) [
73, 32, 108, 111, 118,
101, 32, 99, 117, 112,
99, 97, 107, 101, 115
digest = ArrayBuffer {
[Uint8Contents]: <30 01 7a 5c d9 e2 82 55 6b 55 90 4f 1d de 36 d7 89 dd fb fb 1a 9e a0 cc 5d d8 49 13 38 2f d1 bc>,
byteLength: 32

Custom ESM resolution adjustments

Node.js has removed the –experimental-specifier-resolution flag, because its functionality can be achieved via custom loaders.

Clone the example repository:

git clone

Go to the example directory:

% cd loaders-test/commonjs-extension-resolution-loader

Install the packages:

% yarn install

Here is loaders-test/commonjs-extension-resolution-loader/test/basic-fixtures/index.js:

import { version } from 'process';

import { valueInFile } from './file';
import { valueInFolderIndex } from './folder';

  • Line 1 is unused.
  • ValueInFile is imported from ‘./file’ without specifying the file extension. Without a custom loader, node’s ESM specifier resolution does not automatically resolve file extensions, such as ./file.js or ./file.mjs.

Here is loaders-test/commonjs-extension-resolution-loader/test/basic-fixtures/file.js:

export const valueInFile = ‘hello from file.js’;

  • valueInFolderIndex is imported from ‘./folder’ without specifying the index file name. Without a custom loader, node’s ESM specifier resolution does not have the ability to import directories that include an index file, such as ./folder/index.js or ./folder/index.mjs.

Here is loaders-test/commonjs-extension-resolution-loader/test/basic-fixtures/folder/index.js:

export const valueInFolderIndex = ‘hello from folder/index.js’;

We have mentioned in another article that there are two ways to execute ESM code:

  1. Set “type”: “module” in the package.json.
  2. Change index.js to index.mjs, and run node index.mjs.

Regardless, the following two commands will fail.

% node test/basic-fixtures/index
% node test/basic-fixtures/index.js

However, all these issues can be resolved by the custom loader, loaders-test/commonjs-extension-resolution-loader/loader.js:

import { isBuiltin } from 'node:module';
import { dirname } from 'node:path';
import { cwd } from 'node:process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { promisify } from 'node:util';

import resolveCallback from 'resolve/async.js';

const resolveAsync = promisify(resolveCallback);

const baseURL = pathToFileURL(cwd() + '/').href;

export async function resolve(specifier, context, next) {
const { parentURL = baseURL } = context;

if (isBuiltin(specifier)) {
return next(specifier, context);

// `resolveAsync` works with paths, not URLs
if (specifier.startsWith('file://')) {
specifier = fileURLToPath(specifier);
const parentPath = fileURLToPath(parentURL);

let url;
try {
const resolution = await resolveAsync(specifier, {
basedir: dirname(parentPath),
// For whatever reason, --experimental-specifier-resolution=node doesn't search for .mjs extensions
// but it does search for index.mjs files within directories
extensions: ['.js', '.json', '.node', '.mjs'],
url = pathToFileURL(resolution).href;
} catch (error) {
if (error.code === 'MODULE_NOT_FOUND') {
// Match Node's error code
error.code = 'ERR_MODULE_NOT_FOUND';
throw error;

return next(url, context);

With the loader, the above failed commands work well:

% node --loader=./loader.js test/basic-fixtures/index 
(node:56149) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
hello from file.js
hello from folder/index.js

% node --loader=./loader.js test/basic-fixtures/index.js
(node:56160) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
hello from file.js
hello from folder/index.js

With custom loaders, there is no need for the –experimental-specifier-resolution flag.

V8 JavaScript engine is updated to V8 10.7

Node.js 19 has updated V8 JavaScript engine to V8 10.7, which includes a new function, Intl.NumberFormat, for language-sensitive number formatting.

Intl.NumberFormat(locales, options)