Node.js Best Practices
Full Reference: See advanced.md for worker pool pattern, memory management, cluster mode, graceful shutdown, and performance flags.
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: nodejs for comprehensive documentation.
Event Loop
Phases
┌───────────────────────────┐ ┌─>│ timers │ ← setTimeout, setInterval │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ pending callbacks │ ← I/O callbacks │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ poll │ ← incoming I/O │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ check │ ← setImmediate │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ close callbacks │ └───────────────────────────┘
Priority Order
sync code > process.nextTick > Promises (microtasks) > timers > setImmediate
Don't Block the Event Loop
// BAD - blocks event loop function hashSync(data: string): string { return crypto.pbkdf2Sync(data, 'salt', 100000, 64, 'sha512').toString('hex'); }
// GOOD - use async version async function hashAsync(data: string): Promise<string> { return new Promise((resolve, reject) => { crypto.pbkdf2(data, 'salt', 100000, 64, 'sha512', (err, key) => { if (err) reject(err); else resolve(key.toString('hex')); }); }); }
Async Patterns
// GOOD - parallel execution const [users, posts] = await Promise.all([ fetchUsers(), fetchPosts() ]);
// BAD - sequential when parallel is possible const users = await fetchUsers(); const posts = await fetchPosts(); // waits unnecessarily
// Handle unhandled rejections process.on('unhandledRejection', (reason, promise) => { console.error('Unhandled Rejection:', reason); });
Streams (Pipeline)
import { pipeline } from 'stream/promises'; import { createReadStream, createWriteStream } from 'fs'; import { createGzip } from 'zlib';
// GOOD - handles errors and cleanup await pipeline( createReadStream('input.txt'), createGzip(), createWriteStream('output.txt.gz') );
When NOT to Use This Skill
Scenario Use Instead
Express.js framework backend-express skill
NestJS framework backend-nestjs skill
JavaScript/TypeScript syntax javascript or typescript skills
Testing testing-vitest or testing-jest skills
Database operations Database-specific skills
Anti-Patterns
Anti-Pattern Why It's Bad Correct Approach
Blocking the event loop Freezes all requests Use async APIs or workers
Not handling rejections Silent failures Use process.on('unhandledRejection')
Synchronous file I/O Blocks event loop Use async fs methods
Unbounded caches Memory leaks Use LRU cache with limits
Not removing event listeners Memory leaks Use .off() or .removeListener()
Nested callbacks Callback hell Use async/await
Large sync JSON.parse Blocks event loop Stream parsing or workers
No concurrency limits Resource exhaustion Use p-limit or semaphores
Quick Troubleshooting
Issue Cause Solution
High event loop lag Blocking operations Profile with --inspect, use workers
Memory leak Unbounded cache/listeners Use heap snapshots, fix leaks
"EADDRINUSE" Port already in use Kill process or use different port
"EMFILE: too many open files" File descriptor leak Close files, increase ulimit
Process crashes on error Uncaught exception Add error handlers
Slow startup Too many sync operations Make initialization async
High CPU usage Infinite loop or blocking Profile with --cpu-prof
"MaxListenersExceededWarning" Too many listeners Remove old listeners
Checklist
Development
-
Use async/await over callbacks
-
Handle all Promise rejections
-
Use streams for large data
-
Limit concurrent operations
-
Remove event listeners when done
Production
-
Use cluster mode or PM2
-
Implement graceful shutdown
-
Set appropriate heap size
-
Monitor memory usage
-
Use connection pooling
-
Enable keep-alive for HTTP
Metrics
Metric Target
Event loop lag < 100ms
Heap usage < 70% of limit
Active handles Stable
GC pause < 100ms
Reference Documentation
-
TypeScript
-
Performance