Node.js Core Modules Overview
Node.js ships with a rich standard library of core modules — built-in components compiled directly into the runtime that give you file system access, networking, cryptography, streams, and more without installing a single dependency. Because they live inside the runtime, they load instantly, version in lockstep with Node itself, and never appear in your node_modules folder. Understanding what is available out of the box helps you reach for the standard library before adding third-party packages.
What core modules are
A core module is part of the Node.js binary. When you import fs or http, Node resolves it internally rather than searching the file system, so there is nothing to install and nothing to bundle. They are the foundation that most npm packages are themselves built on top of.
Core modules work with both module systems. With ES modules (the modern default) you use import; with CommonJS you use require. Either way the module name resolves to the same built-in code.
// ES modules (recommended)
import { readFile } from 'node:fs/promises';
// CommonJS (legacy, still widely used)
const { readFile } = require('node:fs/promises');
The node: prefix
Modern Node.js supports an explicit node: prefix for importing built-ins, for example node:fs instead of fs. The prefix is strongly recommended: it tells Node (and human readers) unambiguously that you mean the built-in module, removing any chance that a same-named package in node_modules could shadow it. It also lets Node skip the file-system resolution step entirely.
import os from 'node:os';
import path from 'node:path';
import { createHash } from 'node:crypto';
const id = createHash('sha256').update(os.hostname()).digest('hex').slice(0, 12);
console.log(`Host fingerprint: ${id}`);
console.log(`Config path: ${path.join(os.homedir(), '.myapp', 'config.json')}`);
Output:
Host fingerprint: 9f2c1ab47de0
Config path: /home/alice/.myapp/config.json
Some modules, such as
node:testandnode:sea, are only importable with thenode:prefix. Adopting the prefix everywhere keeps your imports consistent and future-proof.
Core modules vs npm packages
The two look similar at the call site but differ in important ways.
| Aspect | Core modules | npm packages |
|---|---|---|
| Installation | Built in, nothing to install | npm install required |
In node_modules? | No | Yes |
| Versioning | Tied to your Node.js version | Independent (package.json) |
| Resolution | Internal, instant | File-system lookup |
| Examples | fs, http, crypto | express, axios, zod |
A practical rule: prefer a core module when it covers your need (e.g. native fetch over axios, node:test over a test framework for simple cases), and reach for npm when you need higher-level ergonomics or features the standard library does not provide.
The most important core modules
These are the modules you will use most often in real applications.
| Module | Purpose |
|---|---|
node:fs / node:fs/promises | Read, write, and watch files and directories |
node:path | Build and normalize file system paths cross-platform |
node:http / node:https | Create HTTP(S) servers and clients |
node:os | Query CPU, memory, network, and OS information |
node:crypto | Hashing, HMAC, ciphers, and random bytes |
node:stream | Composable readable/writable data pipelines |
node:events | The EventEmitter base for event-driven APIs |
node:url | Parse and construct URLs and URLSearchParams |
node:util | Helpers like promisify, inspect, and parseArgs |
node:process | Environment variables, argv, and lifecycle signals |
node:zlib | Gzip, Brotli, and deflate compression |
node:dns | Resolve hostnames and DNS records |
A quick example that combines several of them — a tiny HTTP server that reports system load:
import { createServer } from 'node:http';
import os from 'node:os';
const server = createServer((req, res) => {
const body = JSON.stringify({
platform: os.platform(),
cpus: os.cpus().length,
loadAvg: os.loadavg(),
freeMem: os.freemem(),
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(body);
});
server.listen(3000, () => {
console.log('Listening on http://localhost:3000');
});
Output:
Listening on http://localhost:3000
Promise-based variants
Several older callback-based modules now offer a promise-based namespace that pairs cleanly with async/await. The most common is node:fs/promises, but node:dns/promises, node:timers/promises, and node:stream/promises exist too.
import { readFile } from 'node:fs/promises';
const pkg = JSON.parse(await readFile('package.json', 'utf8'));
console.log(`Project: ${pkg.name}@${pkg.version}`);
Output:
Project: [email protected]
Prefer the
/promisesvariants in new code. They avoid callback nesting and integrate naturally withtry/catchfor error handling.
Best Practices
- Always import built-ins with the
node:prefix to avoid shadowing and clarify intent. - Reach for the standard library first — native
fetch,crypto, andnode:testcover many needs without dependencies. - Use the
/promisesnamespaces (node:fs/promises,node:dns/promises) withasync/awaitinstead of callback APIs. - Use
node:pathfor any path manipulation so your code stays correct on Windows, macOS, and Linux. - Pin to an active LTS release (Node 20 or 22) so the core module APIs you rely on are stable and supported.
- Avoid naming your own files or npm packages after core modules (
crypto.js,events.js) to prevent confusing resolution bugs.