Error Handling
Proper error handling is crucial for building robust Node.js applications. Errors will happen - network failures, invalid input, bugs in code. How you handle these errors determines whether your application crashes or gracefully recovers. This lesson covers essential error handling patterns.
Types of Errors
1. Programmer Errors (Bugs)
Bugs in your code that should be fixed:
// TypeError - accessing property on undefined
const user = undefined;
console.log(user.name); // TypeError!
// ReferenceError - using undefined variable
console.log(nonExistent); // ReferenceError!
// SyntaxError - invalid code
const x = {; // SyntaxError!
2. Operational Errors
Expected errors that can occur during normal operation:
// File not found
fs.readFile('missing.txt', callback);
// Network error
fetch('https://api.example.com/data');
// Invalid user input
const age = parseInt('not-a-number');
// Database connection failed
await db.connect();
try/catch
The fundamental error handling mechanism:
try {
// Code that might throw
const data = JSON.parse(userInput);
processData(data);
} catch (error) {
// Handle the error
console.error('Error:', error.message);
} finally {
// Always runs (optional)
cleanup();
}
Catching Specific Errors
try {
doSomething();
} catch (error) {
if (error instanceof TypeError) {
console.log('Type error:', error.message);
} else if (error instanceof SyntaxError) {
console.log('Syntax error:', error.message);
} else {
throw error; // Re-throw unknown errors
}
}
Creating Custom Errors
Define custom error classes for your application:
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
this.statusCode = 400;
}
}
class NotFoundError extends Error {
constructor(resource, id) {
super(`${resource} with id ${id} not found`);
this.name = 'NotFoundError';
this.statusCode = 404;
}
}
class AuthenticationError extends Error {
constructor(message = 'Authentication required') {
super(message);
this.name = 'AuthenticationError';
this.statusCode = 401;
}
}
Async Error Handling
Callbacks
Always check the error parameter first:
fs.readFile('file.txt', (err, data) => {
if (err) {
console.error('Error reading file:', err.message);
return; // Important! Stop execution
}
console.log(data);
});
Promises
Use .catch() for error handling:
fetchData()
.then(data => processData(data))
.then(result => saveResult(result))
.catch(error => {
console.error('Error:', error.message);
});
// Or handle at each step
fetchData()
.catch(err => {
console.error('Fetch failed');
return defaultData; // Provide fallback
})
.then(data => processData(data));
Async/Await
Use try/catch with async/await:
async function processUser(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
return { user, orders };
} catch (error) {
console.error('Error:', error.message);
throw error; // Re-throw if caller should handle
}
}
Centralized Error Handling
In web applications, use centralized error handlers:
// Error handling middleware
function errorHandler(err, req, res, next) {
// Log error
console.error(err);
// Handle known operational errors
if (err.isOperational) {
return res.status(err.statusCode).json({
error: {
message: err.message,
code: err.name
}
});
}
// Handle unknown errors (bugs)
res.status(500).json({
error: {
message: 'Internal server error',
code: 'INTERNAL_ERROR'
}
});
}
Process-Level Error Handling
Handle errors at the process level for safety:
// Catch unhandled Promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
// Log and potentially exit
});
// Catch uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// Clean up and exit - process is in undefined state
process.exit(1);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
Error Handling Best Practices
1. Fail Fast
Validate early and throw on invalid input:
function processPayment(amount, currency) {
// Validate immediately
if (typeof amount !== 'number' || amount <= 0) {
throw new ValidationError('Invalid amount');
}
if (!['USD', 'EUR', 'GBP'].includes(currency)) {
throw new ValidationError('Unsupported currency');
}
// Process payment...
}
2. Be Specific
Throw specific errors, not generic ones:
// Bad
throw new Error('Something went wrong');
// Good
throw new NotFoundError('User', userId);
throw new ValidationError('Email format is invalid', 'email');
throw new AuthenticationError('Token expired');
3. Don't Swallow Errors
Always handle or re-throw errors:
// Bad - error is ignored
try {
riskyOperation();
} catch (e) {
// Silently swallowing error
}
// Good - log and handle
try {
riskyOperation();
} catch (e) {
console.error('Operation failed:', e.message);
throw e; // Or handle appropriately
}
4. Provide Context
Include relevant information in errors:
class DatabaseError extends AppError {
constructor(operation, table, originalError) {
super(`Database ${operation} failed on ${table}`);
this.operation = operation;
this.table = table;
this.originalError = originalError;
}
}
Error Response Patterns
REST API Errors
// Standard error response format
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "age", "message": "Must be a positive number" }
]
}
}
Mapping Errors to HTTP Status
const errorStatusMap = {
ValidationError: 400,
AuthenticationError: 401,
ForbiddenError: 403,
NotFoundError: 404,
ConflictError: 409,
RateLimitError: 429,
InternalError: 500
};
function getStatusCode(error) {
return errorStatusMap[error.name] || 500;
}
Key Takeaways
- Distinguish between programmer errors (bugs) and operational errors
- Use try/catch for synchronous code, .catch() or try/catch for async
- Create custom error classes for your application
- Always handle errors - never swallow them silently
- Use centralized error handling in web applications
- Handle process-level errors (unhandledRejection, uncaughtException)
- Fail fast - validate input early
- Provide specific, contextual error messages
Summary
Proper error handling makes your Node.js applications more robust and easier to debug. You've learned how to handle errors in both synchronous and asynchronous code, create custom error classes, implement centralized error handling, and follow best practices for error management.
Next, you'll learn debugging techniques to help you find and fix errors in your Node.js applications.

