Chapter 21: Core Modules
1. What are Core Modules?
Core modules (also called built-in modules) are modules that come pre-installed with every Node.js installation.
You do not need to run npm install to use them — they are part of Node.js itself.
You only need to import or require them.
Most important characteristics (2025–2026):
- They are very stable (almost never break with new Node versions)
- They are optimized and written in C++ under the hood
- Many of them now have promise-based APIs (node:fs/promises, node:timers/promises, etc.)
- All modern code should use the node: prefix when importing them → import fs from ‘node:fs/promises’ (recommended style)
2. The most important core modules (with real usage examples)
I’ll list the ones you will use most often in real projects, ordered by how frequently they appear in typical applications.
1. node:path – File & directory paths
Why it’s important: Never concatenate paths manually (folder + ‘/’ + file) — it breaks on Windows vs Linux/macOS.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import path from 'node:path'; // Recommended way in 2025–2026 const filePath = path.join(__dirname, 'data', 'users.json'); const absolutePath = path.resolve('config', 'settings.json'); console.log(path.basename(filePath)); // → users.json console.log(path.dirname(filePath)); // → .../data console.log(path.extname(filePath)); // → .json console.log(path.parse(filePath)); // object with root, dir, base, ext, name |
Very common pattern:
|
0 1 2 3 4 5 6 7 8 9 10 |
import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); |
2. node:fs & node:fs/promises – File system
Most used module in backend development
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Modern promise-based style (recommended) import fs from 'node:fs/promises'; async function readConfig() { try { const data = await fs.readFile('config.json', 'utf-8'); return JSON.parse(data); } catch (err) { console.error('Cannot read config:', err.message); throw err; } } |
Common operations 2026 style:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
// Check if file exists try { await fs.access('data.txt'); console.log('File exists'); } catch { console.log('File does NOT exist'); } // Create directory (recursive) await fs.mkdir('logs/2026/02', { recursive: true }); // Write file await fs.writeFile('output.txt', 'Hello from Node.js', 'utf-8'); // Append await fs.appendFile('log.txt', `${new Date().toISOString()} - User logged in\n`); // Read directory const files = await fs.readdir('src', { withFileTypes: true }); for (const file of files) { console.log(file.isDirectory() ? '[DIR]' : '[FILE]', file.name); } |
3. node:http & node:https – Low-level HTTP server & client
Most people use Express / Fastify / Hono, but understanding the core is useful.
Minimal HTTP server (classic example)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import { createServer } from 'node:http'; const server = createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end(`Hello from Node.js core HTTP server!\nPath: {req.url}`); }); server.listen(3000, () => { console.log('Server running → http://localhost:3000'); }); |
Modern fetch-style client (Node.js 18+)
|
0 1 2 3 4 5 6 7 8 |
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1'); const todo = await response.json(); console.log(todo); |
4. node:os – Operating system information
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
import os from 'node:os'; console.log('Platform:', os.platform()); // → linux, darwin, win32 console.log('CPU architecture:', os.arch()); // → x64, arm64 console.log('Total memory:', os.totalmem() / 1024 / 1024 / 1024, 'GB'); console.log('Free memory:', os.freemem() / 1024 / 1024 / 1024, 'GB'); console.log('Home directory:', os.homedir()); console.log('Number of CPUs:', os.cpus().length); |
Very useful pattern: Decide behavior based on OS
|
0 1 2 3 4 5 6 7 |
const isWindows = os.platform() === 'win32'; const separator = isWindows ? '\\' : '/'; |
5. node:process – Information & control of current Node process
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
console.log('Process ID:', process.pid); console.log('Node version:', process.version); console.log('Current working directory:', process.cwd()); console.log('Environment variables:', process.env.NODE_ENV); // Very common: read CLI arguments const args = process.argv.slice(2); console.log('Arguments:', args); // Exit with status code // process.exit(0); // success // process.exit(1); // error // Listen for termination signals process.on('SIGINT', () => { console.log('Received SIGINT – graceful shutdown...'); process.exit(0); }); |
6. node:events – Event emitter (foundation of many APIs)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { EventEmitter } from 'node:events'; const emitter = new EventEmitter(); emitter.on('user:login', (user) => { console.log(`${user.name} just logged in`); }); emitter.on('user:login', (user) => { console.log(`Sending welcome email to ${user.email}`); }); emitter.emit('user:login', { name: 'Aman', email: 'aman@example.com' }); |
Many core modules inherit from EventEmitter:
- http.Server
- stream.Readable / Writable
- net.Server / Socket
- child_process.ChildProcess
7. Modern pattern – using the node: prefix (best practice 2025–2026)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
// Recommended style import fs from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; import http from 'node:http'; // Old style (still works, but not recommended anymore) import fs from 'fs'; |
Why node: prefix?
- Makes it clear it’s a core module (not from npm)
- Prevents name collisions if someone publishes an npm package called fs
- Better IDE auto-complete & documentation
8. Quick reference table – most used core modules
| Module | Promise-based version | Main use cases | Frequency in real projects |
|---|---|---|---|
| node:path | — | Path manipulation, __dirname, join, resolve | ★★★★★ |
| node:fs/promises | Yes | Read/write files, mkdir, readdir, access | ★★★★★ |
| node:process | — | env, argv, cwd, exit, pid, platform | ★★★★☆ |
| node:os | — | CPU count, memory, platform, homedir | ★★★☆☆ |
| node:http | — | Create raw HTTP servers (rarely used directly) | ★★☆☆☆ |
| node:events | — | Custom events, EventEmitter | ★★★★☆ |
| node:stream | — | Streaming data (files, http, compression) | ★★★☆☆ |
| node:child_process | — | Run external commands, spawn processes | ★★☆☆☆ |
| node:crypto | Yes (webcrypto) | Hashing, encryption, random bytes | ★★★☆☆ |
| node:util | Yes (promisify) | promisify, inspect, format | ★★☆☆☆ |
Summary – Quick mental checklist
- Use node: prefix in imports
- Prefer promise-based versions (fs/promises, timers/promises, etc.)
- Use path.join / path.resolve — never string concatenation for paths
- process.env is your main way to read configuration
- fs.promises is the standard way to read/write files in modern code
- EventEmitter is the foundation of almost all async APIs in Node
Which core module or pattern would you like to explore much deeper next?
- Deep dive into node:stream (very powerful but tricky)
- Using node:child_process in real projects
- node:crypto – hashing, JWT signing, random tokens
- node:worker_threads – escaping single-thread limitations
- Complete example project using 8–10 core modules together
Just tell me what interests you most — I’ll continue with concrete, copy-paste-ready examples. 😊
