Node.js features that replace popular npm packages
For years, a fresh Node.js project meant a pile of small dependencies before you wrote a line of real code: a test runner, a file watcher, a .env loader, a TypeScript runner, an HTTP client. Node has quietly absorbed most of those jobs. On Node.js 26, here are the built-ins that let you delete dependencies — every example below was run on a stock node:26-slim Docker image.
Testing — node:test instead of Jest or Mocha
Node ships a full test runner with a TAP/spec reporter, watch mode, and coverage. For most projects it removes the need for Jest or Mocha entirely.
// sum.test.mjs
import { test } from 'node:test';
import assert from 'node:assert';
test('adds numbers', () => {
assert.strictEqual(1 + 2, 3);
});$ node --test
ℹ tests 1
ℹ pass 1
ℹ fail 0Watch mode — --watch instead of nodemon
Node reruns your entry point on file changes natively. No more nodemon for local development.
node --watch app.js
# add --watch-path to scope which directories to watchEnvironment variables — --env-file instead of dotenv
Node reads a .env file for you, so dotenv and the import 'dotenv/config' line at the top of every file can go.
$ node --env-file=.env -e "console.log('API_KEY =', process.env.API_KEY)"
API_KEY = secret123TypeScript — native type stripping instead of ts-node
Node runs .ts files directly by erasing the type annotations — no build step, no ts-node or tsx, and on Node 26 no flag either.
// app.ts
const greet = (name: string): string => `hi ${name}`;
console.log(greet('node26'));$ node app.ts
hi node26One caveat: this strips types, it doesn't transform. Type-only features that emit code — enum, namespaces with runtime output, experimentalDecorators — aren't supported. Stick to "erasable" syntax and it just works.
HTTP requests — global fetch instead of node-fetch or axios
fetch, Request, Response, and Headers are global. For most request/response work you no longer need node-fetch or axios.
const res = await fetch('https://api.github.com/repos/nodejs/node');
const data = await res.json();
console.log(data.stargazers_count);Running scripts — node --run instead of npm run
node --run executes a package.json script directly, skipping npm's startup overhead — noticeably faster for scripts you run constantly.
$ node --run hello
ran via node --runTerminal colors — util.styleText instead of chalk
Node has built-in ANSI styling that respects NO_COLOR and whether the output is a TTY, covering the common case that used to pull in chalk.
import { styleText } from 'node:util';
console.log(styleText('green', 'done'));
console.log(styleText(['bold', 'red'], 'error'));Deep clone — structuredClone instead of lodash.clonedeep
const original = { a: 1, nested: { b: [2, 3] } };
const copy = structuredClone(original);
copy.nested.b.push(4);
console.log(original.nested.b); // [2, 3] — original untouchedTwo more worth knowing
node:sqlite— a built-in synchronous SQLite driver (DatabaseSync), covering much of whatbetter-sqlite3is used for, with no native build step.fs.glob/fs.globSync— native glob matching, replacing theglobpackage for many scripts.
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:');
db.exec('CREATE TABLE t(x)');
db.prepare('INSERT INTO t VALUES(?)').run(42);
console.log(db.prepare('SELECT x FROM t').get()); // { x: 42 }Audit your dependencies
None of this means every package is obsolete — Jest's ecosystem, axios interceptors, and chalk's richer API still have their place. But for a new project, the platform now covers testing, watching, env files, TypeScript, HTTP, cloning, and more out of the box. Before adding the next dependency, check whether Node already does it.