Proper error handling and debugging are essential skills for Node.js developers. This guide covers everything from basic error patterns to advanced debugging techniques.
1. Node.js Error Types
Built-in Error Types
Error: Generic error constructorSyntaxError: Parsing errorsReferenceError: Undefined variablesTypeError: Wrong type operationsRangeError: Out-of-range values
Common Operational Errors
- Failed I/O operations
- Network connectivity issues
- Database query failures
- Invalid user input
- Resource exhaustion
2. Basic Error Handling Patterns
Try/Catch Blocks
try {
// Synchronous code that might throw
JSON.parse(invalidJson);
} catch (err) {
console.error('Parsing failed:', err.message);
// Handle or rethrow
}
Error-First Callbacks
fs.readFile('nonexistent.txt', (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
console.error('File not found');
} else {
console.error('Unknown error:', err);
}
return;
}
// Process data
});
Promise Error Handling
fetchData()
.then(data => processData(data))
.catch(err => {
console.error('Processing failed:', err);
// Handle or rethrow
});
3. Advanced Error Handling
Custom Error Classes
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
// Usage
throw new AppError('Invalid input', 400);
Global Error Handler
// Express error handling middleware
app.use((err, req, res, next) => {
err.statusCode = err.statusCode || 500;
err.status = err.status || 'error';
if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: err.status,
error: err,
message: err.message,
stack: err.stack
});
} else {
// Production: send minimal info
res.status(err.statusCode).json({
status: err.status,
message: err.message
});
}
});
4. Debugging Techniques
Console Debugging
// Debug with labels and objects
console.log('[DEBUG] User data:', { id: user.id, email: user.email });
// Time operations
console.time('dbQuery');
await User.find();
console.timeEnd('dbQuery');
Node.js Debugger
// Start app in debug mode
node inspect app.js
// Chrome DevTools
chrome://inspect
// VS Code debugging
// Add to launch.json:
{
"type": "node",
"request": "launch",
"name": "Debug App",
"program": "${workspaceFolder}/app.js"
}
Debugging Async Code
// Use async_hooks for complex flows
const async_hooks = require('async_hooks');
const asyncHook = async_hooks.createHook({
init(asyncId, type, triggerAsyncId) {
console.log(`Async ${type} started`);
},
destroy(asyncId) {
console.log(`Async ${asyncId} destroyed`);
}
});
asyncHook.enable();
5. Logging Strategies
Structured Logging
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({
filename: 'error.log',
level: 'error'
}),
new winston.transports.File({
filename: 'combined.log'
})
]
});
// Usage
logger.error('Database connection failed', {
error: err.message,
timestamp: new Date()
});
Logging Middleware
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
logger.info({
method: req.method,
url: req.url,
status: res.statusCode,
responseTime: `${Date.now() - start}ms`,
ip: req.ip
});
});
next();
});
6. Production Error Monitoring
Process Management
// Handle uncaught exceptions
process.on('uncaughtException', err => {
logger.error('Uncaught Exception:', err);
// Perform cleanup
process.exit(1); // Mandatory (according to Node.js docs)
});
// Handle unhandled rejections
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection:', reason);
// Can choose to exit or continue
});
Monitoring Tools
- Sentry: Error tracking
- New Relic: Performance monitoring
- Datadog: Full-stack observability
- ELK Stack: Log analysis
7. Performance Debugging
CPU Profiling
// Start with --inspect flag
node --inspect app.js
// Take CPU snapshot
const inspector = require('inspector');
const session = new inspector.Session();
session.connect();
session.post('Profiler.enable', () => {
session.post('Profiler.start', () => {
// Run operations to profile
setTimeout(() => {
session.post('Profiler.stop', (err, { profile }) => {
require('fs').writeFileSync('profile.cpuprofile',
JSON.stringify(profile));
});
}, 10000);
});
});
Memory Leak Detection
const heapdump = require('heapdump');
// Trigger heap dump on signal
process.on('SIGUSR2', () => {
const filename = `heapdump-${Date.now()}.heapsnapshot`;
heapdump.writeSnapshot(filename, err => {
if (err) console.error(err);
else console.log(`Heap dump written to ${filename}`);
});
});
// Or use --heapsnapshot-signal flag
node --heapsnapshot-signal=SIGUSR2 app.js
Next: Testing Node.js Applications →
Debugging Checklist
- ✅ Reproduce the issue consistently
- ✅ Check application logs
- ✅ Isolate the problematic code
- ✅ Verify inputs and outputs
- ✅ Check for resource leaks
- ✅ Profile performance bottlenecks
