17 Common Node.js Errors and How to Fix Them

A practical guide to understanding, debugging, and resolving the most frequent Node.js errors you'll encounter in production applications.

Every Node.js developer encounters errors--it's an inevitable part of building robust applications. But understanding what causes these errors and how to resolve them efficiently can mean the difference between hours of debugging and quick, confident fixes. This guide covers the 17 most common Node.js errors you'll encounter, complete with practical solutions and code examples you can apply immediately.

Understanding Node.js Errors

Node.js errors fall into two primary categories: operational errors and programmer errors. Operational errors occur during runtime and represent expected failure scenarios--like network timeouts or file permission issues. Programmer errors, on the other hand, are bugs in your code that require code changes to fix. Understanding this distinction is crucial for implementing appropriate error handling strategies. As covered in Honeybadger's comprehensive guide to Node.js error handling, this foundational understanding shapes how you approach error recovery and prevention in your applications.

Node.js provides several built-in error classes that help categorize issues effectively. The Error base class contains essential properties like message, name, and stack that provide context for debugging. System errors, identified by codes like ENOENT, ECONNRESET, and EACCES, indicate operating system-level issues that typically require different handling than JavaScript errors such as TypeError or ReferenceError.

When an error occurs in Node.js, the error object carries valuable diagnostic information. The code property distinguishes system errors, the errno property provides numeric error codes, and the syscall property identifies the system operation that failed. For JavaScript errors, the name property indicates the error type (SyntaxError, TypeError, RangeError, etc.), while the stack trace points to the exact location in your code where the error originated. This rich error information becomes your primary tool for rapid diagnosis and resolution.

Key characteristics of Node.js errors include:

  • Error objects contain message, name, and stack properties for debugging
  • System errors (ENOENT, ECONNRESET, etc.) indicate OS-level issues requiring different handling strategies
  • JavaScript errors (TypeError, ReferenceError) indicate code issues that require code fixes
  • Node.js provides built-in error classes (SyntaxError, TypeError, RangeError, etc.) for different scenarios
  • Error codes (err.code) enable programmatic error handling and retry decisions

By understanding these error fundamentals, you can implement targeted error handling strategies that improve application reliability and reduce debugging time. Our web development services team specializes in building error-resilient Node.js applications that perform reliably in production environments.

For teams building scalable APIs, proper error handling is essential. Learn how to write scalable OpenAPI specifications that include comprehensive error response definitions from the start.

Memory and Process Errors

1. JavaScript heap out of memory

This error occurs when your Node.js process consumes more memory than the V8 engine's default heap limit (approximately 1.4 GB). Common causes include loading large datasets into memory, memory leaks from uncleared caches or event listeners, and processing large files without streaming. According to LogRocket's analysis of common Node.js errors, memory-related errors are among the most frequent issues in production Node.js applications.

Understanding the Heap Limit: The V8 engine allocates memory for the JavaScript heap, which stores objects, strings, and closures. The default limit of approximately 1.4 GB works well for most applications, but memory-intensive operations like data processing, PDF generation, or image manipulation can quickly exhaust this allocation. When the limit is reached, Node.js throws a FATAL ERROR that crashes your process.

Solutions and Best Practices:

  1. Increase Memory Limit: For applications that legitimately need more memory, you can increase the heap limit using the --max-old-space-size flag. This should be a temporary solution while you address the underlying cause.
// Increase to 4GB - use sparingly and only when needed
// node --max-old-space-size=4096 app.js
  1. Implement Streaming for Large Files: Instead of loading entire files into memory, use Node.js streams to process data in chunks.
// BAD: Loading entire file into memory
const largeFile = fs.readFileSync('gigabyte-file.json');
const data = JSON.parse(largeFile);

// GOOD: Using streams for large files
const readStream = fs.createReadStream('large-file.json');
readStream.pipe(JSONStream.parse('items.*'));
 .on('data', (item) => {
 // Process each item individually
 processItem(item);
 });
  1. Profile Memory Usage: Use Chrome DevTools or the built-in process.memoryUsage() API to monitor memory consumption and identify leaks.
// Monitor memory in development
setInterval(() => {
 const usage = process.memoryUsage();
 console.log({
 heapUsed: Math.round(usage.heapUsed / 1024 / 1024) + 'MB',
 heapTotal: Math.round(usage.heapTotal / 1024 / 1024) + 'MB',
 rss: Math.round(usage.rss / 1024 / 1024) + 'MB'
 });
}, 30000);
  1. Use Worker Threads for CPU-Intensive Tasks: Offload heavy computation to worker threads to prevent blocking the main thread and consuming excessive memory.

2. Max listeners exceeded warning

This warning appears when you attach more than 10 listeners to a single EventEmitter. While Node.js allows this by default, it often indicates memory leaks or design issues that should be addressed. As highlighted in Better Stack's Node.js error guide, this warning serves as an early indicator of potential memory problems in event-driven architectures.

Common Causes and Solutions:

// BAD: Accidentally adding duplicate listeners
socket.on('data', handleData);
socket.on('data', handleData); // Duplicate!

// GOOD: Use { once: true } for one-time listeners
socket.once('data', handleData);

// GOOD: Use named functions and check before adding
const handleData = (data) => { /* ... */ };
if (!socket.listenerCount('data')) {
 socket.on('data', handleData);
}

// GOOD: Increase limit explicitly if you genuinely need many listeners
socket.setMaxListeners(50);

Debugging Memory Leaks from Event Listeners:

  • Use eventEmitter.listenerCount(eventName) to check current listener count
  • Use eventEmitter.listeners(eventName) to see all registered listeners
  • Implement cleanup in component unmount handlers (for frameworks like React)
  • Use weak references where appropriate to allow garbage collection

Network and Connection Errors

3. ECONNRESET (Connection Reset by Peer)

This error occurs when the remote end of a TCP connection closes the connection abruptly. It commonly happens when making HTTP requests to servers that timeout, when clients close browsers before responses complete, or when proxy servers terminate connections. According to Better Stack's comprehensive guide, ECONNRESET is one of the most frequent network errors in distributed systems.

Handling ECONNRESET Gracefully:

// Handle ECONNRESET with retry logic
async function fetchWithRetry(url, retries = 3, delay = 1000) {
 for (let i = 0; i < retries; i++) {
 try {
 return await axios.get(url, { 
 timeout: 5000,
 // Cancel request on socket hang up
 cancelToken: new axios.CancelToken(cancel => {
 req.on('socket', (socket) => {
 socket.on('close', () => {
 if (!res.writableFinished) {
 cancel('Connection closed before response');
 }
 });
 });
 })
 });
 } catch (err) {
 if (err.code === 'ECONNRESET' && i < retries - 1) {
 console.log(`Connection reset, retrying in ${delay}ms...`);
 await new Promise(r => setTimeout(r, delay));
 delay *= 2; // Exponential backoff
 continue;
 }
 throw err;
 }
 }
}

Best Practices for ECONNRESET:

  • Implement retry logic with exponential backoff
  • Set appropriate timeout values on all HTTP clients
  • Check res.socket.destroyed before writing to response
  • Use connection keep-alive for frequent requests to the same host
  • Configure your load balancer to handle graceful disconnections

4. ETIMEDOUT (Connection Timed Out)

This error indicates that a connection attempt or operation timed out. Causes include slow or unresponsive remote servers, network latency issues, or overly aggressive timeout settings. Properly configured timeouts prevent your application from hanging indefinitely on unresponsive services.

Configuring Timeouts Effectively:

// Configure comprehensive timeouts
const axiosInstance = axios.create({
 timeout: 10000, // 10 seconds for entire request
 connectTimeout: 5000, // 5 seconds to establish connection
 readTimeout: 8000, // 8 seconds for read operations
});

// For database connections, configure pool timeouts
const pool = createPool({
 connectionTimeoutMillis: 10000, // Wait up to 10s for connection
 idleTimeoutMillis: 30000, // Close idle connections after 30s
 maxLifetimeMillis: 600000, // Recycle connections after 10min
});

5. ECONNREFUSED (Connection Refused)

This error occurs when attempting to connect to a service that isn't running or is listening on a different port. It's one of the clearest indicators that the target service is unreachable and often signals configuration issues or service failures.

Health Checks and Fallbacks:

// Check service availability before connecting
async function connectWithHealthCheck(serviceUrl) {
 try {
 const health = await axios.get(`${serviceUrl}/health`, { timeout: 2000 });
 if (health.status !== 200) {
 throw new Error('Service unhealthy');
 }
 return createConnection(serviceUrl);
 } catch (err) {
 if (err.code === 'ECONNREFUSED') {
 console.error('Service not available:', serviceUrl);
 // Implement circuit breaker pattern
 circuitBreaker.recordFailure();
 // Or queue request for later retry
 await queueRequest(serviceUrl);
 }
 throw err;
 }
}

6. ENOTFOUND (DNS Lookup Failed)

This error indicates that the DNS lookup for a hostname failed. Causes include incorrect hostnames, network connectivity issues, or DNS server problems. Implementing DNS caching and fallback strategies improves reliability.

DNS Resolution with Fallback:

// Verify hostname and handle DNS errors with caching
async function resolveWithFallback(hostname) {
 const cacheKey = `dns:${hostname}`;
 const cached = await cache.get(cacheKey);
 if (cached) return cached;

 try {
 const result = await dns.promises.resolve(hostname);
 await cache.set(cacheKey, result, 300); // Cache for 5 minutes
 return result;
 } catch (err) {
 if (err.code === 'ENOTFOUND') {
 // Try alternative DNS or cached value
 const fallback = await cache.get(`${cacheKey}:fallback`);
 if (fallback) return fallback;
 throw new Error(`Could not resolve hostname: ${hostname}`);
 }
 throw err;
 }
}

7. EHOSTUNREACH (Host Unreachable)

This error indicates that no route to the host exists. It typically occurs with network configuration issues, firewall rules blocking traffic, or when trying to reach hosts on unreachable networks. Check firewall configurations and network routing tables when this error occurs.

8. EAI_AGAIN (DNS Lookup Temporarily Failed)

This error occurs during asynchronous DNS lookups when the lookup fails temporarily, often due to DNS server load or network congestion. The solution is to retry the operation with appropriate backoff.

// Handle EAI_AGAIN with automatic retry
async function dnsLookupWithRetry(hostname, retries = 3) {
 for (let i = 0; i < retries; i++) {
 try {
 return await dns.promises.resolve4(hostname);
 } catch (err) {
 if (err.code === 'EAI_AGAIN' && i < retries - 1) {
 await new Promise(r => setTimeout(r, 100 * Math.pow(2, i)));
 continue;
 }
 throw err;
 }
 }
}

When building Node.js applications that make extensive network calls, proper error handling for DNS and connection errors is critical. For applications working with databases, our guide on [using Sequelize with TypeScript](/resources/guides/web-development/using-sequelize-with-typescript/) covers database connection error handling patterns.

File System Errors

9. ENOENT (No Such File or Directory)

This is one of the most common errors, occurring when attempting to access files or directories that don't exist. It happens with relative path issues, race conditions where files are deleted, or incorrect path construction. As documented in LogRocket's Node.js error guide, ENOENT frequently occurs in file-heavy applications like configuration loaders and asset managers.

Robust File Handling:

// BAD: Assuming file exists without checking
const content = fs.readFileSync('config.json');

// GOOD: Check and handle missing file with async/await
async function readConfig(path) {
 try {
 const content = await fs.promises.readFile(path, 'utf-8');
 return JSON.parse(content);
 } catch (err) {
 if (err.code === 'ENOENT') {
 console.log(`Config file not found at ${path}, using defaults`);
 return defaultConfig;
 }
 throw err;
 }
}

// Use path.resolve to avoid relative path issues
const configPath = path.resolve(process.cwd(), 'config', 'app.json');

// Verify file exists before reading with fs.stat
async function safeReadFile(filePath) {
 try {
 await fs.promises.access(filePath, fs.constants.F_OK);
 return await fs.promises.readFile(filePath, 'utf-8');
 } catch (err) {
 if (err.code === 'ENOENT') return null;
 throw err;
 }
}

10. EISDIR (Is a Directory)

This error occurs when attempting to read or write to a path that is a directory, not a file. This commonly happens when code assumes a path is a file but receives a directory path instead.

Type Checking Before Operations:

// Check if path is file before operations
async function readFileSafely(filePath) {
 const stats = await fs.promises.stat(filePath);
 if (stats.isDirectory()) {
 throw new Error(`Path is a directory, not a file: ${filePath}`);
 }
 return fs.promises.readFile(filePath, 'utf-8');
}

// Helper function for recursive directory creation
async function ensureDirectoryExists(dirPath) {
 try {
 await fs.promises.mkdir(dirPath, { recursive: true });
 } catch (err) {
 if (err.code !== 'EEXIST') throw err;
 }
}

11. ENOTDIR (Not a Directory)

This error occurs when a path component exists but is not a directory, typically when trying to traverse through a file as if it were a directory. This often happens when path manipulation code makes incorrect assumptions about the filesystem structure.

12. EACCES (Permission Denied)

This error indicates insufficient permissions to access a file or directory. Common causes include running Node.js as a different user than the file owner, missing execute permissions on directories, or SELinux/AppArmor restrictions on Linux systems.

Handling Permission Errors Gracefully:

// Handle permission errors with fallback strategies
async function accessWithFallback(filePath) {
 const fallbackPaths = [
 filePath,
 path.join(process.cwd(), 'data', path.basename(filePath)),
 path.join(os.homedir(), '.app', path.basename(filePath))
 ];

 for (const tryPath of fallbackPaths) {
 try {
 return await fs.promises.readFile(tryPath, 'utf-8');
 } catch (err) {
 if (err.code === 'EACCES') {
 console.warn(`Permission denied: ${tryPath}, trying next location`);
 continue;
 }
 throw err;
 }
 }
 throw new Error('No accessible file path found');
}

13. EEXIST (File Already Exists)

This error occurs when attempting to create a file that already exists, typically when using file creation flags incorrectly or during concurrent write operations.

Safe File Creation Patterns:

// Use proper flags for file operations
async function writeConfig(configPath, data) {
 try {
 // Creates new file, fails if exists (exclusive create)
 await fs.promises.writeFile(configPath, JSON.stringify(data), {
 flag: 'wx', // exclusive create - fails if file exists
 });
 } catch (err) {
 if (err.code === 'EEXIST') {
 // File exists, read and merge instead of overwriting
 const existing = JSON.parse(await fs.promises.readFile(configPath));
 const merged = { ...existing, ...data };
 await fs.promises.writeFile(configPath, JSON.stringify(merged, null, 2));
 } else {
 throw err;
 }
 }
}

// For atomic writes, write to temp file then rename
async function atomicWrite(filePath, data) {
 const tempPath = `${filePath}.${process.pid}.tmp`;
 await fs.promises.writeFile(tempPath, JSON.stringify(data));
 await fs.promises.rename(tempPath, filePath);
}

14. EPERM (Operation Not Permitted)

This error indicates the operation isn't permitted by the system, often related to protected files, read-only filesystems, or system security policies. Unlike EACCES, which relates to user permissions, EPERM typically involves system-level restrictions.

Common EPERM Scenarios:

  • Writing to files opened with exclusive access
  • Modifying files on read-only mounted filesystems
  • Accessing files restricted by security policies
  • Operations on sockets or special device files

For robust file operations, always validate write permissions before attempting modifications and implement proper error handling that distinguishes between different permission-related errors. If your application processes financial data, understanding precise error handling becomes even more critical--see our guide on storing and retrieving precise monetary values with Dinero.js.

Process and IPC Errors

15. Error: write EPIPE (Broken Pipe)

This error occurs when writing to a pipe or socket whose read end has been closed. It commonly happens when piping output to programs that exit early, when using process substitution, or when network connections close unexpectedly during write operations. The EPIPE error is particularly common in CI/CD pipelines and when using process chaining.

Handling EPIPE in Process Pipelines:

// Handle EPIPE in process pipelines gracefully
const child = spawn('processing-program');
const output = fs.createWriteStream('output.txt');

child.stdout.pipe(output);

child.on('error', (err) => {
 if (err.code === 'EPIPE') {
 // The downstream consumer closed before we finished writing
 console.log('Output consumer closed early, completing with partial data');
 } else {
 console.error('Process error:', err);
 }
});

// Prevent EPIPE by checking stream status before writing
function safeWrite(stream, data) {
 if (stream.writableEnded || stream.destroyed) {
 console.warn('Output stream already closed, data not written');
 return;
 }
 stream.write(data);
}

16. ERR_UNHANDLED_REJECTION (Unhandled Promise Rejection)

This error occurs when a promise rejection isn't handled. Since Node.js 15, unhandled rejections cause the process to crash by default, making proper promise handling critical for production applications. As emphasized in Honeybadger's Node.js error handling guide, unhandled promise rejections represent one of the most dangerous error types in modern Node.js applications.

Ensuring All Promises Are Handled:

// BAD: Unhandled rejection that will crash the process
Promise.reject(new Error('Operation failed'));

// GOOD: Always handle promises with .catch()
Promise.reject(new Error('Operation failed'))
 .catch(err => {
 console.error('Caught rejection:', err.message);
 // Log to error tracking service
 errorTracker.captureException(err);
 });

// In async functions, always use try/catch
async function fetchData(url) {
 try {
 return await axios.get(url);
 } catch (err) {
 // Handle or rethrow with context
 console.error(`Failed to fetch ${url}:`, err.message);
 throw new FetchError(`Failed to fetch ${url}`, err);
 }
}

// Set up global safety net for unexpected rejections
process.on('unhandledRejection', (reason, promise) => {
 console.error('CRITICAL: Unhandled Promise Rejection at:', promise);
 console.error('Reason:', reason);
 // Send to error tracking service
 errorTracker.captureException(reason);
 // Consider graceful shutdown for critical errors
 if (isCriticalError(reason)) {
 gracefulShutdown();
 }
});

// Utility to wrap async functions with error handling
function asyncHandler(fn) {
 return (...args) => {
 Promise.resolve(fn(...args)).catch(err => {
 console.error(`Async handler error in ${fn.name}:`, err);
 });
 };
}

17. SyntaxError: Unexpected token

This error occurs when the JavaScript engine encounters code that doesn't follow proper syntax. Common causes include parsing JSON with errors, using ES features without proper transpilation, importing modules incorrectly, or string concatenation issues in dynamic code generation. Syntax errors are caught at parse time and prevent your code from running entirely.

Preventing and Handling Syntax Errors:

// Always validate JSON before parsing
function safeJsonParse(str, fallback = null) {
 try {
 return JSON.parse(str);
 } catch (err) {
 if (err instanceof SyntaxError) {
 console.error('Invalid JSON syntax:', err.message);
 return fallback;
 }
 throw err;
 }
}

// Validate module syntax before dynamic evaluation
async function validateAndLoadModule(code) {
 try {
 // First validate the syntax
 new Function(code);
 // Then load the module
 return await loadModule(code);
 } catch (err) {
 if (err instanceof SyntaxError) {
 throw new ModuleSyntaxError('Invalid module syntax', err);
 }
 throw err;
 }
}

// Use ESLint or TypeScript for syntax validation during development
// Configure your build process to catch syntax errors before runtime

Debugging Syntax Errors Effectively:

  • Syntax errors include line and column numbers--check the exact location
  • Common causes: missing commas, unmatched braces, trailing commas in older Node.js
  • Use ESLint with strict rules to catch syntax issues during development
  • Enable strict mode ('use strict') to catch common mistakes early
  • Consider using TypeScript for additional compile-time error detection

Understanding these process-level errors helps you build more robust Node.js applications. Our team specializes in Node.js development services and can help you implement comprehensive error handling strategies for your production applications. For developers working with TypeScript and design patterns, our guide on understanding design patterns in TypeScript and Node.js provides foundational knowledge for writing maintainable, error-resistant code.

Best Practices for Error Handling

Distinguish Error Types

Understanding the distinction between operational and programmer errors is fundamental to building reliable Node.js applications. According to Honeybadger's comprehensive guide, treating these error types differently leads to better error handling strategies and more maintainable code.

Operational errors are expected failures that occur during normal operation and should be handled gracefully:

  • Network timeouts and connection failures (ECONNRESET, ETIMEDOUT, ECONNREFUSED)
  • File not found errors (ENOENT) when files may be missing
  • Permission denied errors (EACCES) based on user access levels
  • Rate limiting responses (429 Too Many Requests)
  • Third-party service unavailability

Programmer errors are bugs that indicate issues in your code that require fixes:

  • Type errors from incorrect data types or undefined values
  • Reference errors from accessing undefined variables or properties
  • Logic errors in algorithms or business logic
  • Incorrect async/await patterns causing race conditions
  • Memory leaks from uncleared references

The key principle: handle operational errors gracefully with retry logic, fallbacks, and user-friendly messages; fix programmer errors in your code through testing, code review, and debugging.

Use Error Objects Consistently

Creating a consistent error handling pattern with custom error classes improves code organization and debugging efficiency.

// Always throw Error objects, not primitives
throw new Error('Something went wrong');

// Create custom error classes for specific scenarios
class ValidationError extends Error {
 constructor(message, field, value) {
 super(message);
 this.name = 'ValidationError';
 this.field = field;
 this.value = value;
 this.timestamp = new Date();
 }
}

class DatabaseError extends Error {
 constructor(message, query, parameters) {
 super(message);
 this.name = 'DatabaseError';
 this.query = query;
 this.parameters = parameters;
 this.timestamp = new Date();
 }
}

class AuthenticationError extends Error {
 constructor(message, userId) {
 super(message);
 this.name = 'AuthenticationError';
 this.userId = userId;
 this.timestamp = new Date();
 }
}

// Throw custom errors with context
function validateUserInput(input) {
 if (!input.email) {
 throw new ValidationError('Email is required', 'email', input.email);
 }
 if (!input.password || input.password.length < 8) {
 throw new ValidationError('Password must be at least 8 characters', 'password', '***');
 }
}

Implement Retry Strategies

Implementing intelligent retry logic for transient errors improves application reliability without overwhelming failing services.

// Retry with exponential backoff and jitter
async function withRetry(fn, options = {}) {
 const maxRetries = options.maxRetries || 3;
 const baseDelay = options.baseDelay || 1000;
 const maxDelay = options.maxDelay || 30000;
 const retryOn = options.retryOn || (() => true);

 let lastError;

 for (let i = 0; i <= maxRetries; i++) {
 try {
 return await fn();
 } catch (err) {
 lastError = err;

 // Don't retry programmer errors
 if (!isOperationalError(err)) {
 throw err;
 }

 // Don't retry if retryOn returns false
 if (retryOn(err, i) === false) {
 throw err;
 }

 if (i < maxRetries) {
 // Exponential backoff with jitter
 const delay = Math.min(
 baseDelay * Math.pow(2, i) + Math.random() * 100,
 maxDelay
 );
 console.log(`Retry ${i + 1}/${maxRetries} after ${delay}ms: ${err.code}`);
 await new Promise(resolve => setTimeout(resolve, delay));
 }
 }
 }

 throw lastError;
}

// Identify operational errors that are safe to retry
function isOperationalError(err) {
 return [
 'ECONNRESET',
 'ETIMEDOUT',
 'ECONNREFUSED',
 'EAI_AGAIN',
 'ENOTFOUND',
 'ETIMEDOUT',
 'ENETUNREACH',
 'EHOSTUNREACH'
 ].includes(err.code);
}

// Usage example
const userData = await withRetry(
 () => fetchUserFromService(userId),
 { maxRetries: 3, baseDelay: 500 }
);

Centralize Error Handling

Centralized error handling consolidates error processing logic, making applications more maintainable and ensuring consistent error responses across your API. This pattern is essential for production applications where error tracking and monitoring are critical.

Express.js Error Middleware:

// Centralized error handling middleware for Express
app.use((err, req, res, next) => {
 // Log structured error information
 logger.error({
 level: 'error',
 message: err.message,
 stack: err.stack,
 code: err.code,
 name: err.name,
 path: req.path,
 method: req.method,
 userId: req.user?.id,
 requestId: req.requestId,
 timestamp: new Date().toISOString()
 });

 // Handle specific error types with appropriate responses
 if (err instanceof ValidationError) {
 return res.status(400).json({ 
 error: 'Validation Error',
 message: err.message,
 field: err.field 
 });
 }

 if (err instanceof AuthenticationError) {
 return res.status(401).json({ 
 error: 'Unauthorized',
 message: 'Authentication required' 
 });
 }

 if (err instanceof DatabaseError) {
 return res.status(503).json({ 
 error: 'Service Unavailable',
 message: 'Database operation failed' 
 });
 }

 // Handle known system errors with user-friendly messages
 if (err.code === 'ECONNREFUSED') {
 return res.status(503).json({ 
 error: 'Service Unavailable',
 message: 'External service temporarily unavailable' 
 });
 }

 // Default: Internal server error
 res.status(500).json({ 
 error: 'Internal Server Error',
 message: process.env.NODE_ENV === 'production' 
 ? 'An unexpected error occurred' 
 : err.message
 });
});

Global Node.js Error Handlers:

// Handle uncaught exceptions - should trigger graceful shutdown
process.on('uncaughtException', (err) => {
 logger.error('Uncaught Exception:', {
 error: err.message,
 stack: err.stack,
 timestamp: new Date().toISOString()
 });

 // Perform graceful shutdown
 gracefulShutdown(1);
});

// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
 logger.error('Unhandled Rejection:', {
 reason: reason?.message || reason,
 stack: reason?.stack,
 promise: promise.toString(),
 timestamp: new Date().toISOString()
 });

 // Don't exit on unhandled rejection in Node 15+
 // Just log and continue (or trigger alert)
 alertService.send('Unhandled Promise Rejection detected');
});

// Graceful shutdown handler
function gracefulShutdown(exitCode = 0) {
 logger.info('Shutting down gracefully...');

 // Close HTTP server
 server.close(() => {
 logger.info('HTTP server closed');
 // Close database connections
 database.end(() => {
 logger.info('Database connections closed');
 process.exit(exitCode);
 });
 });

 // Force shutdown after timeout
 setTimeout(() => {
 logger.error('Forced shutdown after timeout');
 process.exit(1);
 }, 10000);
}

Log Errors Effectively

Structured logging with consistent formatting enables better debugging and easier log analysis in production environments.

// Structured logging for better debugging
function logError(err, context = {}) {
 const logEntry = {
 level: 'error',
 message: err.message,
 stack: err.stack,
 code: err.code,
 name: err.name,
 timestamp: new Date().toISOString(),
 service: 'api',
 version: process.env.npm_package_version,
 environment: process.env.NODE_ENV,
 ...context
 };

 // Console output for development
 console.error(JSON.stringify(logEntry, null, 2));

 // Send to logging service in production
 if (process.env.NODE_ENV === 'production') {
 loggerService.send(logEntry);
 }
}

// Usage with request context
app.use((req, res, next) => {
 req.requestId = generateRequestId();
 req.logger = (err) => logError(err, {
 requestId: req.requestId,
 path: req.path,
 method: req.method,
 userId: req.user?.id
 });
 next();
});

Implementing these centralized error handling patterns creates a foundation for reliable, maintainable Node.js applications. Combined with proper monitoring and alerting, you'll be able to detect and resolve issues before they impact users. For teams implementing NLP features in Node.js, proper error handling becomes even more critical--see our guide on natural language processing in Node.js for specialized error handling patterns in AI-powered applications.

Performance Considerations

Error handling directly impacts application performance in several ways. Excessive try/catch blocks in hot code paths can affect execution speed, while synchronous error logging can block the event loop. Understanding these performance implications helps you design error handling strategies that don't compromise application speed.

Performance Impact of Error Handling:

  • Try/catch overhead: While modern V8 optimizes try/catch blocks, they still have some performance cost in hot paths. Reserve detailed error handling for boundary layers rather than inner loops.
  • Synchronous logging: Writing logs synchronously blocks the event loop. Use asynchronous logging libraries like Winston or Pino to prevent this.
  • Error object creation: Creating Error objects with stack traces has a cost. In performance-critical code, consider conditional error creation.
  • Retry storms: Aggressive retry strategies can overwhelm failing services. Implement circuit breakers and rate limiting.

Debugging and Monitoring Tools:

// Use Node.js built-in debugger for runtime inspection
// node --inspect app.js
// Then connect with Chrome DevTools

// Generate heap snapshots to identify memory-related errors
const v8 = require('v8');
const fs = require('fs');

function takeHeapSnapshot(filename) {
 const snapshotFile = v8.writeHeapSnapshot(filename);
 console.log(`Heap snapshot written to: ${snapshotFile}`);
}

// Take snapshot on memory warning
process.on('warning', (warning) => {
 if (warning.name === 'MaxMemoryExceeded') {
 takeHeapSnapshot(`heap-${Date.now()}.heapsnapshot`);
 }
});

// Use clinic.js for performance profiling
// npm install -g clinic
// clinic doctor -- node app.js

Circuit Breaker Pattern:

// Implement circuit breaker to prevent cascade failures
class CircuitBreaker {
 constructor(options = {}) {
 this.failureThreshold = options.failureThreshold || 5;
 this.resetTimeout = options.resetTimeout || 30000;
 this.state = 'CLOSED';
 this.failures = 0;
 this.lastFailure = null;
 }

 async execute(fn) {
 if (this.state === 'OPEN') {
 if (Date.now() - this.lastFailure > this.resetTimeout) {
 this.state = 'HALF_OPEN';
 } else {
 throw new Error('Circuit breaker is OPEN');
 }
 }

 try {
 const result = await fn();
 this.onSuccess();
 return result;
 } catch (err) {
 this.onFailure();
 throw err;
 }
 }

 onSuccess() {
 this.failures = 0;
 this.state = 'CLOSED';
 }

 onFailure() {
 this.failures++;
 this.lastFailure = Date.now();
 if (this.failures >= this.failureThreshold) {
 this.state = 'OPEN';
 }
 }
}

// Usage with external service calls
const breaker = new CircuitBreaker({ failureThreshold: 3, resetTimeout: 30000 });

async function fetchFromExternalService() {
 return breaker.execute(() => axios.get('https://external-api.com/data'));
}

Key Performance Tips:

  • Use asynchronous logging (Winston, Pino, Bunyan) to avoid blocking the event loop
  • Implement circuit breaker patterns for all external service calls
  • Set appropriate timeouts on all I/O operations to prevent hanging
  • Monitor error rates with alerting for anomaly detection
  • Use worker threads for CPU-intensive error processing tasks
  • Profile memory usage regularly to catch leaks before they cause crashes
  • Consider using APM tools like New Relic, Datadog, or Elastic APM for production monitoring

By implementing these performance-conscious error handling strategies, your Node.js applications will remain responsive and reliable even under error conditions. Our web development team has extensive experience building high-performance Node.js applications with robust error handling.

Frequently Asked Questions

Build More Reliable Node.js Applications

17

Common Errors Covered

5

Error Categories

10+

Code Examples

100%

Production Ready Patterns

Need Help with Your Node.js Application?

Our experienced Node.js development team can help you build robust, error-resistant applications. From architecture review to error handling implementation, we've got you covered.

Sources

  1. LogRocket Blog - 17 common Node.js errors - Comprehensive coverage of common Node.js errors with practical solutions and code examples
  2. Better Stack Community - 16 Common Errors in Node.js - Detailed guide covering system-related errors with root causes and resolution strategies
  3. Honeybadger - Comprehensive Guide to Error Handling in Node.js - In-depth coverage of error types, patterns for error delivery, and best practices for error handling
  4. Node.js Documentation - Errors - Official Node.js documentation on error handling and error codes