Node ships with a standard library. fs, path, os and events cover most of what a backend needs before you install a single package.
Learning objectives
- Read, write and inspect files with
fs. - Build safe paths with
pathinstead of string concatenation. - Read system information with
os. - Emit and listen for custom events with
EventEmitter.
1. fs — the file system
Synchronous calls block until the operation finishes. They are fine in scripts and at startup:
const fs = require("node:fs");
fs.writeFileSync("notes.txt", "first line\n", "utf8");
const text = fs.readFileSync("notes.txt", "utf8");
console.log(text);
fs.appendFileSync("notes.txt", "second line\n");
console.log(fs.existsSync("notes.txt")); // true
Asynchronous calls keep the process free while the disk works:
const fs = require("node:fs/promises");
async function main() {
await fs.writeFile("notes.txt", "hello", "utf8");
const text = await fs.readFile("notes.txt", "utf8");
console.log(text);
}
main();
Inside an HTTP server, always use the async version — a synchronous read blocks every other request.
Useful operations:
fs.mkdirSync("data", { recursive: true }); // create folders, ignore if present
fs.readdirSync("."); // names in a directory
fs.statSync("notes.txt").size; // bytes
fs.unlinkSync("notes.txt"); // delete a file
2. path — never join with "+"
const path = require("node:path");
path.join("content", "posts", "one.md"); // content\posts\one.md on Windows
path.resolve("./data"); // absolute path from cwd
path.extname("app.js"); // ".js"
path.basename("/tmp/report.pdf"); // "report.pdf"
path.dirname("/tmp/report.pdf"); // "/tmp"
path.join normalises separators for the current operating system. Manual string concatenation breaks on Windows and on paths that already end with a slash.
__dirname (CommonJS) and import.meta.dirname (ESM) give the folder of the current file — the reliable anchor for reading project files regardless of where the process was started.
3. os — information about the machine
const os = require("node:os");
os.platform(); // "win32", "linux", "darwin"
os.cpus().length; // logical cores
os.totalmem(); // bytes
os.homedir(); // C:\Users\...
Useful for diagnostics and for choosing sensible defaults (for example, a worker count).
4. events — EventEmitter
Node's core is event-driven. EventEmitter exposes that pattern to your own code:
const { EventEmitter } = require("node:events");
const bus = new EventEmitter();
bus.on("order:created", (order) => {
console.log(`Send receipt for ${order.id}`);
});
bus.once("ready", () => console.log("Ready once"));
bus.emit("order:created", { id: 42 });
bus.emit("order:created", { id: 43 });
on stays subscribed; once removes itself after the first call. HTTP servers, streams and sockets are all EventEmitters underneath.
Common mistakes
- Forgetting the
"utf8"argument and getting aBufferback. - Using
__dirnameinside an ES module ("type": "module") — useimport.meta.dirname. - Blocking the event loop with
readFileSyncinside a request handler.
Practice
Write the directory report tool in the assignment. It combines fs, path and process.argv into a script you will actually reuse.