Node.js Domain Skill
Purpose
Provide expert-level guidance on Node.js runtime patterns, server architecture, performance optimization, and production deployment. This skill covers the entire Node.js ecosystem from event loop internals to clustering strategies.
Key Patterns
1. Event Loop Awareness
The event loop is single-threaded. Never block it.
const data = fs.readFileSync('/large-file.json');
const parsed = JSON.parse(data);
const stream = fs.createReadStream('/large-file.json');
const parsed = await pipeline(stream, new JSONParseStream());
Phases to understand:
- Timers:
setTimeout, setInterval callbacks
- Pending callbacks: I/O callbacks deferred from previous cycle
- Poll: Retrieve new I/O events; execute I/O callbacks
- Check:
setImmediate callbacks
- Close:
socket.on('close') callbacks
Use setImmediate() to yield to the event loop during CPU-intensive synchronous work:
function processLargeArray(array, callback) {
const CHUNK = 1000;
let index = 0;
function doChunk() {
const limit = Math.min(index + CHUNK, array.length);
for (; index < limit; index++) {
processItem(array[index]);
}
if (index < array.length) {
setImmediate(doChunk);
} else {
callback();
}
}
doChunk();
}
2. Streams
Always prefer streams for large data. The four stream types:
import { Readable, Writable, Transform, pipeline } from 'node:stream';
import { pipeline as pipelineAsync } from 'node:stream/promises';
class CSVToJSON extends Transform {
constructor() {
super({ objectMode: true });
this.headers = null;
}
_transform(chunk, encoding, callback) {
const line = chunk.toString().trim();
if (!this.headers) {
this.headers = line.split(',');
return callback();
}
const values = line.split(',');
const obj = Object.fromEntries(
this.headers.map((h, i) => [h, values[i]])
);
this.push(obj);
();
}
}
(
fs.(),
(),
({
: ,
() {
(, .(obj) + );
}
}),
fs.()
);
Backpressure handling: Always respect writable.write() returning false:
async function* generateData() {
for (let i = 0; i < 1_000_000; i++) {
yield Buffer.from(`line ${i}\n`);
}
}
await pipelineAsync(
Readable.from(generateData()),
fs.createWriteStream('output.txt')
);
3. Worker Threads
Use for CPU-intensive operations. NOT for I/O (the event loop handles I/O efficiently).
import { Worker } from 'node:worker_threads';
import { cpus } from 'node:os';
class WorkerPool {
#workers = [];
#queue = [];
#activeWorkers = 0;
constructor(workerPath, poolSize = cpus().length - 1) {
this.workerPath = workerPath;
this.poolSize = poolSize;
}
async execute(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
if (this.#activeWorkers < this.poolSize) {
this.#runTask(task);
} else {
this.#queue.push(task);
}
});
}
#runTask(task) {
this.#activeWorkers++;
const worker = new Worker(this.workerPath, {
workerData: task.data
});
worker.on('message', {
task.(result);
.#activeWorkers--;
(.#queue. > ) {
.#(.#queue.());
}
});
worker.(, {
task.(err);
.#activeWorkers--;
(.#queue. > ) {
.#(.#queue.());
}
});
}
() {
(.#activeWorkers > ) {
( (r, ));
}
}
}
pool = ();
results = .(
files.( pool.({ : file }))
);
4. Clustering
Use the cluster module or PM2 for multi-process scaling:
import cluster from 'node:cluster';
import { cpus } from 'node:os';
import process from 'node:process';
const WORKERS = parseInt(process.env.WEB_CONCURRENCY) || cpus().length;
if (cluster.isPrimary) {
console.log(`Primary ${process.pid} starting ${WORKERS} workers`);
for (let i = 0; i < WORKERS; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.error(`Worker ${worker.process.pid} died (${signal || code})`);
if (code !== 0) {
console.log('Starting replacement worker...');
cluster.fork();
}
});
} else {
const app = createServer();
app.listen(process.env.PORT || );
.();
}
5. Graceful Shutdown
Always implement graceful shutdown in production:
class GracefulServer {
#server;
#connections = new Set();
#isShuttingDown = false;
constructor(app) {
this.#server = app.listen(process.env.PORT || 3000);
this.#server.on('connection', (conn) => {
this.#connections.add(conn);
conn.on('close', () => this.#connections.delete(conn));
});
process.on('SIGTERM', () => this.shutdown());
process.on('SIGINT', () => this.shutdown());
}
async shutdown() {
if (this.#isShuttingDown) return;
this.#isShuttingDown = true;
console.log('Graceful shutdown initiated...');
this.#server.close( {
.();
process.();
});
( conn .#connections) {
conn.();
}
( {
.();
( conn .#connections) {
conn.();
}
process.();
}, );
}
}
Best Practices
- Use
node: protocol for built-in modules: import fs from 'node:fs/promises'
- Prefer
node:fs/promises over callback-based fs
- Use
AbortController for cancellable operations
- Set
--max-old-space-size appropriately for memory-intensive apps
- Enable source maps in production:
--enable-source-maps
- Use
node --watch for development (Node 18+)
- Validate environment variables at startup with libraries like
envalid
- Use structured logging (pino, winston) -- never
console.log in production
- Set proper
keep-alive timeouts on HTTP servers (must exceed load balancer timeout)
- Always handle
unhandledRejection and uncaughtException**
process.on('unhandledRejection', (reason, promise) => {
logger.error({ reason, promise }, 'Unhandled rejection');
});
process.on('uncaughtException', (error) => {
logger.fatal({ error }, 'Uncaught exception');
process.exit(1);
});
Common Pitfalls
| Pitfall | Impact | Fix |
|---|
Blocking event loop with JSON.parse on large payloads | All requests stall | Use streaming JSON parser or worker threads |
| Not handling backpressure in streams | Memory exhaustion | Use pipeline(), check .write() return value |
| Memory leaks from event listeners | OOM crashes | Remove listeners on cleanup, use AbortController |
Using cluster with stateful sessions | Inconsistent state | Use Redis for session storage |
Not setting server.keepAliveTimeout | 502 errors behind load balancers | Set higher than LB timeout (e.g., 65s) |
| Sync operations in async context | Degraded throughput | Audit for sync calls: readFileSync, execSync |
Ignoring ERR_USE_AFTER_CLOSE | Silent failures | Always check stream state before operations |
Performance Checklist