Node.js8 min read
Error Handling Best Practices in Node.js
Learn proper error handling in Node.js. Handle sync/async errors, create custom errors, and build robust applications.
Sarah Chen
December 19, 2025
0.0k0
Error Handling Best Practices in Node.js
Errors happen. How you handle them defines your app's reliability.
Sync vs Async Errors
// Synchronous - use try/catch
try {
JSON.parse('invalid json');
} catch (error) {
console.error('Parse error:', error.message);
}
// Asynchronous - different approaches
// Callbacks: error-first pattern
// Promises: .catch()
// Async/await: try/catch
Async/Await Error Handling
async function fetchUser(id) {
try {
const user = await User.findById(id);
if (!user) throw new Error('User not found');
return user;
} catch (error) {
console.error('fetchUser error:', error.message);
throw error; // Re-throw if caller should handle
}
}
Custom Error Classes
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404);
}
}
class ValidationError extends AppError {
constructor(message = 'Validation failed') {
super(message, 400);
}
}
// Usage
throw new NotFoundError('User not found');
Express Error Handler
// Error handling middleware (must have 4 params)
app.use((err, req, res, next) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
const message = err.isOperational ? err.message : 'Internal server error';
res.status(statusCode).json({
status: 'error',
message,
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
});
Async Route Wrapper
// Catches async errors automatically
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Usage
app.get('/users/:id', asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new NotFoundError();
res.json(user);
}));
Unhandled Rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection:', reason);
// Log to monitoring service
// Don't exit in development, exit in production
});
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// Must exit after uncaught exception
process.exit(1);
});
Validation Errors
// With express-validator
const { validationResult } = require('express-validator');
app.post('/users', [
body('email').isEmail(),
body('password').isLength({ min: 6 })
], (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
status: 'error',
errors: errors.array()
});
}
// Continue...
});
Database Errors
async function createUser(data) {
try {
return await User.create(data);
} catch (error) {
if (error.code === 11000) { // MongoDB duplicate key
throw new ValidationError('Email already exists');
}
if (error.name === 'ValidationError') {
throw new ValidationError(error.message);
}
throw error; // Unknown error, let it bubble
}
}
Complete Error Setup
// errors/AppError.js
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.isOperational = true;
}
}
// middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
err.statusCode = err.statusCode || 500;
if (process.env.NODE_ENV === 'development') {
res.status(err.statusCode).json({
status: 'error',
message: err.message,
stack: err.stack
});
} else {
// Production: don't leak error details
res.status(err.statusCode).json({
status: 'error',
message: err.isOperational ? err.message : 'Something went wrong'
});
}
};
// middleware/asyncHandler.js
const asyncHandler = fn => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
// app.js
app.use('/api/users', userRoutes);
app.use(errorHandler); // Must be last
Key Takeaway
Use custom error classes for different error types. Wrap async routes with asyncHandler. Have a central error middleware. Handle unhandled rejections. Never expose stack traces in production. Good error handling makes debugging easier and apps more reliable.
#Node.js#Error Handling#Best Practices#Intermediate