15 Recent Node.js Features that Replace Popular npm Packages
Over the years, Node.js developers have relied on countless npm packages to fill gaps in the platform. From HTTP utilities to file system helpers, the ecosystem has always been one of Node’s greatest strengths. But as Node.js continues to evolve, many features that once required third-party packages are now built into the runtime itself.
This shift reduces dependency bloat, improves security, and makes applications easier to maintain. if you want a tool to track security risks from 3rd party packages in your Node.js check out N|Solid
In this post, we’ll look at some of the most notable Node.js features that replace popular npm packages.
**1. node-fetch
→ Global fetch()
Before: Developers installed node-fetch
to use the familiar browser fetch()
API in Node.js.
Now: Starting in Node.js 18, fetch()
is a global function, identical to the browser implementation.
const res = await fetch('https://api.github.com/repos/nodejs/node');
const data = await res.json();
console.log(data.full_name); // "nodejs/node"
When added: Introduced in Node.js v17.5.0 (experimental)
Stabilized: Became stable (no longer experimental) in Node.js v18.0.0
When to still use node-fetch
: Only if you need older Node.js compatibility (pre-18).
**2. ws
(client) → Global WebSocket
Before: The ws
package was the go-to for WebSocket clients and servers.
Now: Node.js includes a global WebSocket
class for client-side connections:
const ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = () => ws.send('Hello!');
ws.onmessage = (event) => console.log('Received:', event.data);
When added: Experimental global WebSocket client support added in Node.js v21.0.0
Stabilized: As of current available sources, still experimental (no stable version declared yet)
When to still use ws
: For server-side WebSocket implementations, ws
or libraries built on top of it remain the standard.
**3. Test frameworks → node:test
Before: Testing required libraries like mocha
, jest
, or tap
.
Now: Node.js includes node:test
, a built-in test runner:
import test from 'node:test';
import assert from 'node:assert';
test('addition works', () => {
assert.strictEqual(2 + 2, 4);
});
When added: Introduced (experimental) in Node.js v18.0.0
Stabilized: Considered stable starting with Node.js v20.0.0
When to still use third-party frameworks: If you need features like snapshots, mocking, or rich plugin ecosystems. Take into account that node:test is enough for modules, but for some of the bigger frameworks (e.g. full-stack features) are still useful when it comes to application development.
**4. sqlite3
/ better-sqlite3
→ node:sqlite [experimental]
Before: Developers relied on native bindings like sqlite3
or the faster better-sqlite3
. These required compilation and often broke on upgrades.
Now: Node.js is introducing an experimental node:sqlite
module:
import { open } from 'node:sqlite';
const db = await open(':memory:');
await db.exec('CREATE TABLE users (id INTEGER, name TEXT)');
Is still a experimental feature
When to still use community packages: If you need advanced performance tuning or features not yet available in the experimental API.
**5. chalk
/ kleur
→ util.styleText()
Before: Libraries like chalk
and kleur
dominated console styling.
Now: Node.js provides util.styleText()
:
import { styleText } from 'node:util';
console.log(styleText('red', 'Error!'));
console.log(styleText(['bold', 'green'], 'Success!'));
When added: Introduced in Node.js v20.12.0
Stabilized: Stable as of Node.js v22.17.0
When to still use chalk
: If you need rich theming, chaining syntax, or backwards compatibility.
**6. ansi-colors
/ strip-ansi
→ util.stripVTControlCharacters()
Before: Developers used packages like strip-ansi
to clean escape codes from logs.
Now: Node.js includes util.stripVTControlCharacters()
:
import { stripVTControlCharacters } from 'node:util';
const text = '\u001B[4mUnderlined\u001B[0m';
console.log(stripVTControlCharacters(text)); // "Underlined"
Advantages:
- Native, reliable handling of ANSI codes.
When to still use third-party: Rare—most cases are covered natively now.
**7. glob
→ fs.glob()
Before: The glob
package was essential for file-matching patterns.
Now: Node.js 22+ introduces fs.glob()
:
import fs from 'node:fs/promises';
const files = await fs.glob('**/*.js');
console.log(files);
When added: Added in Node.js v22.0.0 (or in v22 series) as part of fs API expansions; release notes of v22.0 include new fs glob functionality.
Stabilized: It is stable in Node.js 22.17.0 LTS as part of the v22 release line.
When to still use glob
: If you need compatibility with older Node.js versions.
**8. rimraf
→ fs.rm({ recursive: true })
Before: Deleting directories recursively required rimraf
.
Now: Node.js supports recursive removal directly:
import fs from 'node:fs/promises';
await fs.rm('dist', { recursive: true, force: true });
When added: The recursive
option for fs.rm()
has been available since around Node.js v12.10.0 (for legacy callback usages) and in promises API in later versions; but exact patch version for full promise-based fs.rm()
with recursive:true, force:true
may vary. Official docs show fs.rm(path[, options])
including recursive
since Node.js 14+. (Note: force
option is also supported.)
Stabilized: Already stable in all active LTS versions (v18, v20, v22) → no longer experimental.
**9. mkdirp
→ fs.mkdir({ recursive: true })
Before: Developers used mkdirp
to recursively create directories.
Now: Node.js supports it natively:
await fs.mkdir('logs/app', { recursive: true });
When added: The recursive
option for fs.mkdir()
was added in Node.js v10.12.0.
Stabilized: Stable since its addition; it's part of core API and used widely. \
**10. uuid
(v4) → crypto.randomUUID()
Before: UUID generation meant adding the uuid
package.
Now: Node.js includes crypto.randomUUID()
:
import { randomUUID } from 'node:crypto';
console.log(randomUUID());
When added: Introduced in Node.js v14.17.0
Stabilized: Stable since its introduction; part of crypto core module.
**11. base64-js
/ atob
polyfills → Buffer
, atob
, btoa
Before: Encoding/decoding often required polyfills.
Now: Node.js includes atob
and btoa
globals, plus Buffer
:
const encoded = btoa('hello');
console.log(encoded); // "aGVsbG8="
console.log(atob(encoded)); // "hello"
When added: The atob
and btoa
globals were introduced in around Node.js v20.0.0 (or later)
Stabilized: They are part of stable APIs in current Node.js LTS releases.
**12. url-pattern
→ URLPattern [experimental]
Before: Developers relied on url-pattern
for route matching.
Now: Node.js includes the global URLPattern
API:
const pattern = new URLPattern({ pathname: '/users/:id' });
const match = pattern.exec('/users/42');
console.log(match.pathname.groups.id); // "42"
When added: URLPattern
was added in Node.js v20.0.0 as experimental.
Stabilized: As of now, still marked experimental in the Node.js docs (i.e. not yet stable).
**13. dotenv
(basic) → --env-file
flag [experimental]
Before: Loading .env
files required dotenv
.
Now: Node.js can load env files directly:
node --env-file=.env app.js
When added: The --env-file
flag was introduced in Node.js v20.10.0 (experimental).
Stabilized: Not yet stable; still experimental
When to still use dotenv
: If you need advanced features like variable expansion or multiple env files.
**14. event-target-shim
→ EventTarget
Before: Node.js had its own EventEmitter
, and developers used event-target-shim
to get Web-standard EventTarget
.
Now: EventTarget
is available globally:
const target = new EventTarget();
target.addEventListener('ping', () => console.log('pong'));
target.dispatchEvent(new Event('ping'));
When added: Introduced in Node.js v15.0.0
Stabilized: Became stable in Node.js v15.4.0 when it was marked “no longer experimental” in the globals docs.
15. tsc
(basic transpilation) → Node.js Experimental TypeScript Support
Before: Running .ts
files required a full TypeScript toolchain (tsc
or ts-node
).
Now: Node.js includes experimental TypeScript support:
node --experimental-strip-types app.ts
When added: This feature is very recent; the “strip types” experimental flag was introduced in
Node.js v21.0.0 or around that major release. Official doc shows experimental support in the 21.x release line. (Exact minor/patch version not fully documented in sources I found.) \
Stabilized: Not yet stable — still experimental.
When to still use tsc
: For full type-checking, declaration files, and production-grade builds.
Final Thoughts
The evolution of Node.js shows a clear trend: functionality that once required external dependencies is now first-class in core. This shift helps developers:
- Reduce dependency overhead.
- Minimize supply chain and security risks.
- Write code that’s more portable across browsers and servers.
But staying current with these changes isn’t always easy, especially in production environments where stability, performance, and security matter most. That’s where N|Solid comes in.
N|Solid gives you deep insights into your Node.js applications, so you can see exactly how built-in features like fetch
, node:test
, or crypto.randomUUID()
perform under real workloads. And with N|Sentinel, our AI-powered agent, you monitor usage and you get recommendations and even code-level solutions for optimization.
If you’re modernizing your stack and taking advantage of these built-in Node.js features, running on N|Solid ensures you can do it with confidence, performance, and security at scale.