Node.js ships with a rich standard library of built-in modules — no npm install required. These core modules cover file system operations, path manipulation, operating system information, and utility functions that you will use in almost every Node.js project. Understanding them well reduces your dependency on third-party packages, keeps your project lean, and gives you confidence working with the fundamental building blocks that frameworks like Express are built on top of. This lesson covers the four core modules you will use most frequently as a MEAN Stack backend developer.
Core Modules Overview
| Module | Import | Primary Purpose |
|---|---|---|
fs |
require('fs') or require('fs/promises') |
File system — read, write, watch, stat, mkdir, delete |
path |
require('path') |
Cross-platform file path construction and parsing |
os |
require('os') |
Operating system info — CPUs, memory, home directory, hostname |
util |
require('util') |
Utilities — promisify, inspect, format, deprecate, types |
crypto |
require('crypto') |
Hashing, encryption, random bytes, UUID |
url |
require('url') |
URL parsing and construction (WHATWG URL API) |
querystring |
require('querystring') |
Parse and stringify URL query strings (use URLSearchParams now) |
child_process |
require('child_process') |
Spawn child processes, run shell commands |
fs Module — Callback, Promise, and Sync APIs
| Operation | Callback API | Promise API | Sync API |
|---|---|---|---|
| Read file | fs.readFile(path, cb) |
fs.promises.readFile(path) |
fs.readFileSync(path) |
| Write file | fs.writeFile(path, data, cb) |
fs.promises.writeFile(path, data) |
fs.writeFileSync(path, data) |
| Append file | fs.appendFile(path, data, cb) |
fs.promises.appendFile(path, data) |
fs.appendFileSync(path, data) |
| Check exists | — | fs.promises.access(path) |
fs.existsSync(path) |
| Get info | fs.stat(path, cb) |
fs.promises.stat(path) |
fs.statSync(path) |
| List directory | fs.readdir(path, cb) |
fs.promises.readdir(path) |
fs.readdirSync(path) |
| Make directory | fs.mkdir(path, cb) |
fs.promises.mkdir(path) |
fs.mkdirSync(path) |
| Delete file | fs.unlink(path, cb) |
fs.promises.unlink(path) |
fs.unlinkSync(path) |
| Watch file | fs.watch(path, listener) |
— | — |
fs.promises (the Promise-based API) over the callback-based API in modern Node.js code. It integrates naturally with async/await, produces cleaner error handling with try/catch, and avoids callback nesting. Only use the synchronous *Sync versions at application startup (before the server begins listening) where blocking is acceptable — never inside request handlers.path.join() and path.resolve() everywhere — never string-concatenate file paths manually. On Windows, path separators are backslashes (\); on macOS and Linux they are forward slashes (/). path.join(__dirname, 'config', 'settings.json') produces the correct path on every platform automatically. String concatenation like __dirname + '/config/settings.json' breaks on Windows.fs.existsSync(path) followed by fs.readFileSync(path) is a race condition — the file could be deleted between the two calls. Instead, just attempt the operation and handle the error: try { await fs.promises.readFile(path) } catch(e) { if (e.code === 'ENOENT') ... }. ENOENT means “Error NO ENTry” — the file or directory does not exist.Basic Example
const fs = require('fs/promises'); // Promise-based API (Node 14+)
const path = require('path');
const os = require('os');
const util = require('util');
// ── path module ───────────────────────────────────────────────────────────
const configPath = path.join(__dirname, '..', 'config', 'settings.json');
console.log(configPath); // /project/config/settings.json (any platform)
path.basename('/users/alice/resume.pdf'); // 'resume.pdf'
path.dirname('/users/alice/resume.pdf'); // '/users/alice'
path.extname('script.min.js'); // '.js'
path.parse('/users/alice/resume.pdf');
// { root: '/', dir: '/users/alice', base: 'resume.pdf', ext: '.pdf', name: 'resume' }
path.resolve('src', 'index.js'); // absolute path from cwd
path.relative('/app/src', '/app/tests'); // '../tests'
path.normalize('/app//src/../src/./'); // '/app/src/'
// ── fs module — async operations ─────────────────────────────────────────
async function fileOperations() {
const filePath = path.join(__dirname, 'data.txt');
// Write
await fs.writeFile(filePath, 'Hello, Node.js!
Line 2
Line 3', 'utf8');
// Read
const content = await fs.readFile(filePath, 'utf8');
console.log(content);
// Append
await fs.appendFile(filePath, '
Appended line');
// Stat — metadata
const stats = await fs.stat(filePath);
console.log({
size: stats.size, // bytes
isFile: stats.isFile(),
isDirectory:stats.isDirectory(),
created: stats.birthtime,
modified: stats.mtime,
});
// List directory
const entries = await fs.readdir(__dirname, { withFileTypes: true });
entries.forEach(entry => {
const type = entry.isDirectory() ? '[DIR]' : '[FILE]';
console.log(type, entry.name);
});
// Make directory (recursive: true creates parent dirs too)
await fs.mkdir(path.join(__dirname, 'uploads', '2025'), { recursive: true });
// Delete
await fs.unlink(filePath);
}
// ── fs.watch — watch for file changes ────────────────────────────────────
const watcher = require('fs').watch('./config', { recursive: true }, (event, filename) => {
console.log(`[${event}] ${filename}`);
// event: 'rename' (created/deleted) or 'change' (modified)
});
// watcher.close() to stop watching
// ── os module ─────────────────────────────────────────────────────────────
console.log(os.hostname()); // 'my-server'
console.log(os.platform()); // 'linux', 'darwin', 'win32'
console.log(os.arch()); // 'x64', 'arm64'
console.log(os.cpus().length); // number of CPU cores
console.log(os.totalmem()); // total RAM in bytes
console.log(os.freemem()); // free RAM in bytes
console.log(os.homedir()); // '/home/alice' or 'C:/Users/Alice'
console.log(os.tmpdir()); // '/tmp' or 'C:/Temp'
console.log(os.uptime()); // system uptime in seconds
console.log(os.networkInterfaces()); // network interface details
console.log(os.EOL); // '
' on Unix, '
' on Windows
// ── util module ───────────────────────────────────────────────────────────
// util.promisify — convert callback-based functions to Promise-based
const { promisify } = require('util');
const sleep = promisify(setTimeout); // setTimeout(cb, delay) → Promise
await sleep(1000); // await a 1-second delay
// util.inspect — deep stringify any value (better than JSON.stringify)
const complex = { fn: () => 'hello', sym: Symbol('x'), undef: undefined };
console.log(util.inspect(complex, { depth: 4, colors: true }));
// { fn: [Function: fn], sym: Symbol(x), undef: undefined }
// JSON.stringify would silently drop fn, sym, and undef
// util.format — printf-style string formatting
util.format('Hello %s, you are %d years old', 'Alice', 30);
// 'Hello Alice, you are 30 years old'
// util.types — type checking utilities
util.types.isPromise(Promise.resolve()); // true
util.types.isAsyncFunction(async () => {}); // true
util.types.isDate(new Date()); // true
util.types.isRegExp(/abc/); // true
// util.deprecate — mark a function as deprecated
const oldFunction = util.deprecate(
() => 'old result',
'oldFunction() is deprecated. Use newFunction() instead.'
);
Real-World Example: Config File Manager
// config-manager.js
const fs = require('fs/promises');
const path = require('path');
const os = require('os');
class ConfigManager {
#configDir;
#configPath;
#config = {};
#watcher = null;
constructor(appName) {
// Store config in OS-appropriate user config directory
this.#configDir = path.join(os.homedir(), '.config', appName);
this.#configPath = path.join(this.#configDir, 'config.json');
}
async load() {
await fs.mkdir(this.#configDir, { recursive: true });
try {
const raw = await fs.readFile(this.#configPath, 'utf8');
this.#config = JSON.parse(raw);
} catch (err) {
if (err.code === 'ENOENT') {
this.#config = {}; // no config file yet — start empty
} else {
throw new Error(`Failed to load config: ${err.message}`);
}
}
return this;
}
async save() {
const json = JSON.stringify(this.#config, null, 2);
await fs.writeFile(this.#configPath, json, 'utf8');
return this;
}
get(key, defaultValue = undefined) {
return key.split('.').reduce((obj, k) => obj?.[k], this.#config) ?? defaultValue;
}
set(key, value) {
const keys = key.split('.');
let obj = this.#config;
for (let i = 0; i < keys.length - 1; i++) {
obj[keys[i]] = obj[keys[i]] ?? {};
obj = obj[keys[i]];
}
obj[keys.at(-1)] = value;
return this;
}
watch(onChange) {
this.#watcher = require('fs').watch(this.#configPath, async () => {
await this.load();
onChange(this.#config);
});
}
stopWatching() {
this.#watcher?.close();
}
}
// Usage
const config = new ConfigManager('task-manager');
await config.load();
config.set('server.port', 3000).set('server.host', 'localhost');
await config.save();
console.log(config.get('server.port')); // 3000
console.log(config.get('db.uri', 'mongodb://localhost:27017'));
Common Mistakes
Mistake 1 — Building paths with string concatenation
❌ Wrong — breaks on Windows where separator is backslash:
const filePath = __dirname + '/config/' + filename; // fails on Windows
✅ Correct — always use path.join:
const filePath = path.join(__dirname, 'config', filename); // works everywhere
Mistake 2 — Using existsSync before read — race condition
❌ Wrong — file could be deleted between the two calls:
if (fs.existsSync(filePath)) {
const data = fs.readFileSync(filePath); // race condition — may throw!
}
✅ Correct — attempt and handle the error directly:
try {
const data = await fs.promises.readFile(filePath, 'utf8');
} catch (err) {
if (err.code !== 'ENOENT') throw err; // only ignore "not found"
}
Mistake 3 — Forgetting that os.freemem() returns bytes, not MB
❌ Wrong — logs raw bytes, misleadingly large number:
console.log(`Free memory: ${os.freemem()} MB`); // "Free memory: 4294967296 MB" ← wrong
✅ Correct — convert to the desired unit:
const freeMB = (os.freemem() / 1024 / 1024).toFixed(0);
console.log(`Free memory: ${freeMB} MB`); // "Free memory: 4096 MB"
Quick Reference
| Task | Code |
|---|---|
| Read file (async) | await fs.promises.readFile(path, 'utf8') |
| Write file (async) | await fs.promises.writeFile(path, data, 'utf8') |
| Make directory | await fs.promises.mkdir(path, { recursive: true }) |
| File exists check | try { await fs.promises.access(path) } catch { /* not found */ } |
| Join paths | path.join(__dirname, 'folder', 'file.json') |
| Absolute path | path.resolve('relative/path') |
| File extension | path.extname('file.min.js') → '.js' |
| CPU count | os.cpus().length |
| Free memory (MB) | (os.freemem() / 1024 / 1024).toFixed(0) |
| Promisify callback fn | util.promisify(callbackFn) |
| Deep inspect value | util.inspect(value, { depth: 4, colors: true }) |