Node.js 18 Release: What's New
Tags: node
Published: 2022-05-04 11:41:04

The Node.js team announced the release of version 18 on April 19. Node.js 18 has some significant new features that Node developers should be aware of, including:

- The upgrade of the V8 engine to version 10.1.
- An experimental test runner module.
- Most excitingly, experimental support for browser APIs like `fetch` and Streams.

In this article, we'll look at some of the major highlights from this release.

## Long-term Support Release Status for Node.js 18

Node.js 18 is a long-term support (LTS) release, so it will continue to receive updates for 30 months. Because this is a new release, it is considered the current release, meaning development work on this version is ongoing, and we should expect regular updates.

After six months, in October 2022, it will become the "active LTS" release, because version 19 will be released. The active LTS release will continue to receive updates, which may be either bug fixes, security patches, or new features backported from the current release.

Once an LTS release switches to maintenance mode, typically at least two years after release, it will receive security patches and bugfixes almost exclusively.

## V8 Engine Upgrade in Node.js 18

The V8 engine is the JavaScript runtime developed by Google for Chrome, and used by Node.js to execute JavaScript. Any changes to how we use the JavaScript language inside Node.js must come through the V8 engine.

V8's upgrade to 10.1 means that the Array methods `findLast` and `findLastIndex` are now available in Node.js. These methods return the last item or index of an item in an array that passes a filter function.

`Array.findLast` works just like `Array.find`, except it begins at the end of the array and traverses it in reverse order. `Array.findLastIndex` simply returns the item's index, rather than the item itself.

These methods both take a higher-order function as an argument and call this function on each element in the list, returning from the method call when the function returns `true`.

The 10.1 release of the V8 engine also includes minor performance improvements, which you can read about in the release notes on the [V8 project blog](https://v8.dev/blog).

## Node.js 18 Has A Built-in Test Runner

Node.js has included the `assert` library since very early days, making it fairly easy to write simple tests without a third-party library. But good test suites grow quickly, and most projects today depend on a test runner library like Jest, Mocha, Jasmine, or Ava.

These libraries make it easier to organize tests into logical groups, visualize test outcomes, collect coverage data, and simplify setup and teardown.

The `node:test` module included in Node.js version 18 adds a lot of functionality for which you would previously need one of these modules.

The built-in test runner supports subtests, test skipping, limited test runs, and callback tests. Here are a few examples of these features in action:

```js
import assert from "assert";
import test from "node:test";
```

Notice the "node:" prefix, distinguishing this module from any user-created packages named "test".

```js
test("Concatenate user's full name", (t) => {
  const user = new User("John", "Doe");
  assert.strictEqual(user.fullName(), "John Doe");
});
```

The `test` import is a function used to define tests. The first argument is a string describing the test, and the second is the function containing the test logic. This API is roughly the same as Ava's `test`, and takes the place of both `it` and `describe` from Jest.

It covers the same ground as Jest's `describe` because it can also define test groups, with individual tests (or even test subgroups) defined by calling `t.test` within the callback function.

For example, we can create a test group for serialization of a class and individual tests for each type of supported serialization.

```js
test("User class serialization", (t) => {
    const user = new User("John", "Doe");

    await t.test("Serialize to string", (t) => {
        const str = user.toString();
        assert.ok(str.includes("John"));
        assert.ok(str.includes("Doe"));
        ...
    });

    await t.test("Serialize to JSON", (t) => {
        const userJson = user.toJSON();
        assert.doesNotThrow(() => JSON.parse(userJson));
        ...
    });
});
```

Now, the string passed to `test` describes a test group, and each call to `t.test` defines an individual test. Note that `t.test` returns a promise, hence the `await`s in the example.

While it's exciting to see these testing features embraced by the Node.js standard library, I think it's reasonable to expect most projects to use a third-party testing module still.

Many Node devs already have a favorite test library, and it's likely to be a while before the `node:test` core module supports all the features that third-party libraries offer.

## New Browser-compatible APIs in Node.js 18

Commonly supported browser APIs like [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) and [Streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) are among the most significant additions to Node.js 18.

In the browser, `fetch` replaced the cumbersome `XMLHttpRequest` with a flexible, terse HTTP client. The Streams API allows us to perform incremental processing of data that's received from or sent to a network.

It's important to note that these APIs are still considered **experimental** in Node.js 18. It may be wise to avoid their use in mission-critical applications for the moment, as breaking API updates are not out of the question.

### Node.js Now Supports Fetch API

Adding support for `fetch` to Node.js allows us to make HTTP requests concisely without:

- Third-party libraries like `axios` or `node-fetch` (which exposes an API roughly equivalent to the new built-in global)
- Relying on the more complicated `http` and `https` packages to make requests from Node projects.

The existing `http` and `https` packages are very flexible and support advanced functionality. However, the `fetch` global function is much more concise; writing Node.js code will feel more natural to developers used to writing JavaScript for the browser. Additionally, because Node.js and modern browsers now share this API, it is easier to write code to run in both environments.

Here's how we would write an HTTPS request in Node.js using
only first-party modules before the addition of the `fetch` API:

```js
import https from "https";

const data = {
  nameFirst: "John",
  nameLast: "Doe",
};

let responseData = "";

const req = https.request(
  "https://example.org/user",
  { method: "POST" },
  (res) => {
    res.on("data", (data) => {
      responseData += data;
    });

    res.on("error", (error) => console.error(error));
  }
);

req.end(JSON.stringify(data), "utf-8", () => {
  console.log(`Response data: ${responseData}`);
});
```

Notice the heavy reliance on callbacks and the fact that the `https` module does not support promises. We have to register handlers for response data and errors, and be careful not to use the response data variable before the request completes.

While this is a somewhat awkward example, it highlights the
difficulty of using the `https` and `http` modules in Node, and explains why most developers choose to use a third-party module like Axios, request, or node-fetch.

If we were to make the same request in Node 18 using the `fetch` API, it would look something like this:

```js
const data = {
  nameFirst: "John",
  nameLast: "Doe",
};

try {
  const response = await fetch("https://example.org/user", {
    method: "POST",
    body: JSON.stringify(data),
  });

  const responseJson = await response.json();
  console.log(`Response data: ${responseJson}`);
} catch (error) {
  console.error(error);
}
```

This version is decidedly more terse and familiar to JavaScript devs used to modern language features. I/O operations return promises, errors are thrown and caught, and the code reads synchronously from top to bottom.

While there will certainly still be use cases for the `http` and `https` packages and their lower-level capabilities, I think most devs will prefer the new `fetch` global function for daily use.

### Support for Streams API

The Streams API broadly describes a set of interfaces such as `ReadableStream` and `WritableStream`, which allow us to process data incrementally before a whole object loads in memory. The Streams API is actually a prerequisite for `fetch` compatibility because the `body` property of a `fetch` response is a `ReadableStream`.

A common use case for streams is that we want to use a large amount of data, but don't need the entirety of the data to operate on: think multimedia applications or other real-time apps.

While Node.js has always supported these use cases, the availability of the same high-level APIs in both the client and server environments may make developing these applications easier.

## Wrap Up: Ecosystem Implications and Node.js 18 Release Notes

One of the selling points of Node.js, and more broadly, JavaScript-based backends, is that full-stack engineers can work at all levels of a web application's stack without switching languages.

It's clear that the Node team is keen on playing to this advantage through Node.js 18's new features, continuing to narrow the gap between JavaScript in the browser and JavaScript on the server. When we consider the increased prevalence of code that needs to run in both environments, such as server-side rendered websites, it's encouraging to see that gap continue to close.

There have been several smaller changes in Node.js 18, such as changes to internationalization APIs, formalization of the Blob and BroadcastChannel APIs, and more.

If you want to dig into the full set of changes in this version, you should take a look at the [official Node.js 18 release notes](https://nodejs.org/en/blog/announcements/v18-release-announce/).

I hope you enjoy trying out the new features in this exciting new LTS release of Node.js. Happy coding!

**P.S. If you liked this post, [subscribe to our JavaScript Sorcery list](https://blog.appsignal.com/javascript-sorcery) for a monthly deep dive into more magical JavaScript tips and tricks.**

**P.P.S. If you need an APM for your Node.js app, go and [check out the AppSignal APM for Node.js](https://www.appsignal.com/nodejs).**