Node.js Core Modules — File System, HTTP, Streams & EventEmitter
Node.js ships with over 30 built-in modules that provide everything needed for servers, file operations, binary data, OS interaction, and cryptography — without installing any third-party packages.
What You’ll Learn
By the end of this tutorial, you’ll create an HTTP server, read and write files using streams, use EventEmitter for custom events, work with buffers and crypto, and build a CLI file sync tool.
Why Core Modules Matter
Node.js core modules are the foundation everything else builds on. Durga Antivirus Pro uses the crypto module for hash-based threat detection and fs for file scanning. Doda Browser uses http and events for its sync service. DodaZIP uses stream and zlib for archive processing. Understanding core modules means you can build sophisticated tools without external dependencies.
Core Modules Learning Path
flowchart TD
A[Async JS] --> B[Core Modules]
B --> C[Express.js]
B --> D[Database]
B --> E[Advanced]
B --> F{You Are Here}
style F fill:#f90,color:#fff
HTTP Module — Build a Server From Scratch
const http = require("node:http");
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Hello World", url: req.url }));
});
server.listen(3000, () => {
console.log("Server at http://localhost:3000");
});Line by line:
require("node:http")— import the HTTP module (note thenode:prefix — it’s a Node.js core protocol)http.createServer(callback)— creates a server; the callback runs on every requestreq— the request object (method, url, headers)res— the response object (writeHead, end)server.listen(3000)— bind to port 3000
File System (fs) — Read and Write Files
const fs = require("node:fs/promises"); // Promise-based API
// Reading
const data = await fs.readFile("file.txt", "utf8");
// Writing
await fs.writeFile("output.txt", "Hello World", "utf8");
// Appending
await fs.appendFile("log.txt", "New log entry\n");
// Directory operations
await fs.mkdir("new-dir", { recursive: true });
const files = await fs.readdir(".");
await fs.rm("file.txt");
// File info
const stats = await fs.stat("file.txt");
console.log(stats.size, stats.isFile(), stats.mtime);Always use the fs/promises API in modern Node.js. Avoid fs.readFileSync in server code — it blocks the event loop.
Path Module — File Path Manipulation
const path = require("node:path");
console.log(path.join("users", "alice", "docs")); // users/alice/docs
console.log(path.resolve("app", "..", "config")); // /home/user/config
console.log(path.basename("/usr/bin/node")); // node
console.log(path.dirname("/usr/bin/node")); // /usr/bin
console.log(path.extname("image.jpg")); // .jpg
path.join() builds paths correctly across operating systems. path.resolve() gives absolute paths. Never concatenate paths with string templates — use these functions.
Events (EventEmitter) — Publish/Subscribe Pattern
const EventEmitter = require("node:events");
class OrderSystem extends EventEmitter {}
const orders = new OrderSystem();
orders.on("order-placed", (order) => {
console.log(`Order #${order.id} for ${order.customer}`);
});
orders.emit("order-placed", { id: 1001, customer: "Alice" });
// Once — fire only on first event
orders.once("startup", () => console.log("First run only"));EventEmitter follows the publish/subscribe pattern — objects emit named events and attached listeners react. Core Node.js modules use it extensively (HTTP requests, streams, process).
Streams — Process Data Chunk by Chunk
const fs = require("node:fs");
const zlib = require("node:zlib");
const { pipeline } = require("node:stream/promises");
// Read a large file in chunks (doesn't load entire file into memory)
const readStream = fs.createReadStream("large-file.txt", "utf8");
readStream.on("data", (chunk) => console.log("Chunk:", chunk.length));
readStream.on("end", () => console.log("Done"));
// Pipe — connect readable to writable
const read = fs.createReadStream("source.txt");
const write = fs.createWriteStream("dest.txt");
read.pipe(write);
// Compress with pipeline (Node 15+)
await pipeline(
fs.createReadStream("file.txt"),
zlib.createGzip(),
fs.createWriteStream("file.txt.gz")
);Streams are essential for large files — they process data in chunks instead of loading everything into memory. Use pipeline() for proper error handling and cleanup.
Buffer & Crypto
const crypto = require("node:crypto");
// SHA-256 hash
const hash = crypto.createHash("sha256");
hash.update("password123");
console.log(hash.digest("hex"));
// Random bytes
const bytes = crypto.randomBytes(16);
console.log(bytes.toString("hex"));
// Generate UUID
const uuid = crypto.randomUUID();Common Mistakes
1. Using sync fs methods in a server — fs.readFileSync blocks the entire event loop. Always use fs/promises.
2. Not handling stream errors — Streams emit error events. If unhandled, they crash the process. Use .on('error', handler) or pipeline().
3. Modifying a subarray buffer — .subarray() doesn’t copy data — modifying the slice also modifies the original. Use Buffer.from(slice) for a copy.
4. Forgetting res.end() — HTTP responses hang until res.end() is called. Ensure every code path calls it.
5. Exceeding max listeners — Over 10 listeners on an EventEmitter prints a warning. Use emitter.setMaxListeners(n).
Common Patterns
Configuration Loader Using Core Modules
A common real-world task: load JSON config with environment variable overrides, using only built-in modules:
const fs = require("node:fs/promises");
const path = require("node:path");
async function loadConfig(filePath) {
const absPath = path.resolve(filePath);
const raw = await fs.readFile(absPath, "utf8");
const config = JSON.parse(raw);
// Environment variable overrides
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith("APP_")) {
const configKey = key.slice(4).toLowerCase();
config[configKey] = value;
}
}
return config;
}
// Usage
// config.json: { "port": 3000, "db_host": "localhost" }
// env: APP_PORT=4000
// Result: { "port": "4000", "db_host": "localhost" }
loadConfig("./config.json").then(console.log);Expected output (with APP_PORT=4000):
{ port: "4000", db_host: "localhost" }Why it matters: This pattern is used by Doda Browser to merge config file defaults with environment-specific overrides during deployment — no third-party libraries needed.
CLI Watcher Using fs.watch
Monitor a directory for file changes and react using core modules:
const fs = require("node:fs");
const path = require("node:path");
const { EventEmitter } = require("node:events");
class FileWatcher extends EventEmitter {
constructor(dir) {
super();
this.dir = dir;
}
start() {
fs.watch(this.dir, (eventType, filename) => {
const fullPath = path.join(this.dir, filename);
this.emit("change", { eventType, filename, fullPath });
});
}
}
const watcher = new FileWatcher("./uploads");
watcher.on("change", ({ eventType, filename }) => {
console.log(`[${eventType}] ${filename} was modified`);
});
watcher.start();Expected output when a file is added:
[rename] report.pdf was modifiedReal-world use: Durga Antivirus Pro uses fs.watch to detect new files dropped in quarantine folders, triggering an automatic scan without polling.
Retry Logic with EventEmitter
Network operations fail. A retry pattern using events makes the failure handling transparent:
const { EventEmitter } = require("node:events");
async function fetchWithRetry(url, maxRetries = 3) {
const emitter = new EventEmitter();
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const res = await fetch(url);
emitter.emit("success", { attempt, status: res.status });
return res;
} catch (err) {
emitter.emit("retry", { attempt, error: err.message });
if (attempt === maxRetries) throw err;
await new Promise(r => setTimeout(r, 1000 * attempt)); // backoff
}
}
}
const result = fetchWithRetry("https://api.example.com/data");
result.on?.("retry", ({ attempt, error }) => console.log(`Attempt ${attempt} failed: ${error}`));Practice Questions
1. What’s the difference between readFile and createReadStream?
readFile loads the entire file into memory. createReadStream reads it in chunks — suitable for large files.
2. What is the EventEmitter pattern?
A publish-subscribe pattern where objects emit named events and listeners react. Used extensively in Node.js core modules.
3. What stream types exist?
Readable (source), Writable (destination), Duplex (both), Transform (modifies data). Use pipeline() to connect them.
4. What is Buffer used for?
Fixed-size chunk of raw memory for binary data — file contents, network packets, crypto operations.
5. Challenge: Build a file copy tool that uses streams.
const fs = require("node:fs");
const { pipeline } = require("node:stream/promises");
async function copyFile(src, dest) {
await pipeline(fs.createReadStream(src), fs.createWriteStream(dest));
console.log(`Copied ${src} → ${dest}`);
}
copyFile(process.argv[2], process.argv[3]);FAQ
Try It Yourself
Build a File Sync CLI Tool:
#!/usr/bin/env node
const fs = require("node:fs/promises");
const path = require("node:path");
const crypto = require("node:crypto");
async function hashFile(filePath) {
const content = await fs.readFile(filePath);
return crypto.createHash("sha256").update(content).digest("hex");
}
async function syncDirs(source, dest) {
await fs.mkdir(dest, { recursive: true });
const files = await fs.readdir(source);
for (const file of files) {
const srcPath = path.join(source, file);
const destPath = path.join(dest, file);
const stats = await fs.stat(srcPath);
if (stats.isDirectory()) {
await syncDirs(srcPath, destPath);
} else {
let needsCopy = true;
try {
const [srcHash, destHash] = await Promise.all([hashFile(srcPath), hashFile(destPath)]);
needsCopy = srcHash !== destHash;
} catch {}
if (needsCopy) { await fs.copyFile(srcPath, destPath); console.log(`Synced: ${file}`); }
}
}
}
const [,, src, dest] = process.argv;
if (!src || !dest) { console.error("Usage: node file-sync.js <source> <dest>"); process.exit(1); }
syncDirs(src, dest).then(() => console.log("Sync complete"));What’s Next
| Lesson | Description |
|---|---|
| https://tutorials.dodatech.com/backend/nodejs/express/ | Express.js framework |
| https://tutorials.dodatech.com/backend/nodejs/nodejs-database/ | MySQL and MongoDB |
| https://tutorials.dodatech.com/backend/nodejs/nodejs-advanced/ | Clustering, security, WebSockets |
| REST API | Building RESTful APIs |
| Docker | Containerizing Node.js apps |
What’s Next
Congratulations on completing this Nodejs Core Modules tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro