Skip to content

What's New in Node.js 18

Node.js 18 was first  released back in April  this year and it's becoming the Active LTS (Long Term Support) version on 25th October 2022.

At NearForm we have settled on a policy which recommends using the Active LTS for new development, so we're getting ready to migrate our active internal projects and start new ones on Node 18.

You can learn more about the Node.js release schedule  here Node 18 comes with lots of internal improvements and a few user facing new features, which we're going to cover in this article.

Global browser APIs

One of the great things about JavaScript is its ubiquity: you can use the same language both in client and server applications.

As language and APIs evolve, there has also been an ongoing effort to standardize the ways similar operations are done in code running in a browser environment and in a Node.js application. Typical examples of that are sending HTTP requests and streaming data.

Node 18 improves this by introducing support for global browser APIs, meaning that nearly-identical code can now be used for certain activities regardless of whether it's running in the server or the client, and that these APIs are accessible globally, without importing any modules.

Experimental support for global fetch

Node 18 introduces support for the global fetch API, the familiar feature we've come to love in the browser, which avoids using the low-level  XMLHttpRequest  object or external libraries such as  axios .

The built-in Node.js  HTTP client  isn't very user-friendly and a common option was to rely on external libraries such as  node-fetch .

Node 18 introduces experimental support for a global fetch function, which is built on top of  undici  and aims at being compliant with the  Fetch standard .

const response = await fetch('')

if (response.ok) {
  console.log(await response.json())
} else {
  console.error(await response.text())

Experimental support for global Web Streams

Streams  existed in Node.js long before they were introduced in browsers. Unfortunately the browser's  Stream API  is considerably different, so Node.js tried to fill the gaps by providing a browser-compatible  Web Streams API . Web Streams were already available in Node.js, but version 18 makes them accessible in the global namespace.

Here's a fairly convoluted way of turning a string to upper case using Web Streams.

const read = new ReadableStream({
  start (controller) {
    controller.enqueue('hello world')

const transform = new TransformStream({
  transform (chunk, controller) {

const write = new WritableStream({
  write (chunk) {


// outputs: HELLO WORLD

Experimental test runner module

Similar to the fragmentation of APIs for sending HTTP requests, testing tooling is also a rich part of the Node.js ecosystem, which comes with advantages and disadvantages.

Many testing frameworks are available and many have seen a rise and fall in adoption over the years. These days our general approach at NearForm is to test purely Node.js code using  tap  and purely frontend code using  Jest .

We're never too prescriptive and we trust our teams to choose what works best for them. As an example, it's very common to see a monorepo containing both frontend and backend code using a single testing framework, because consistency is also an important quality in a codebase.

Node.js 18 introduces a novelty, a new  node:test  module inspired by  tap .

Module prefixes

A quick digression on module prefixes is in order.

The node:test module is  only available with the node: prefix, and it's the first-ever Node.js module to exhibit this behavior.

import test from 'node:test';

Prefixes were introduced to remove ambiguity and risk of loading unintended, possibly malicious, modules.

Using the testing module

The API of the testing module is largely similar to that of  tap , although many advanced features are still missing, for example  module mocks  and  fixtures.

import test from 'node:test'
import assert from 'assert'

test('sum', async t => {
  await t.beforeEach(async t => {
    t.context = { 
      sum: (a, b) => a + b

  await t.test('1 + 2', t => {
    assert.strictEqual(t.context.sum(1, 2), 3)

What else is there?

Node.js 18 comes with a long list of other improvements, although most of them are not immediately relevant for end users. Check them out in the  official announcement .

Insight, imagination and expertly engineered solutions to accelerate and sustain progress.