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.
Error Handling Best Practices in Node.js
Errors happen. How you handle them defines your app's reliability.
Sync vs Async Errors
```javascript // 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
```javascript 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
```javascript 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
```javascript // 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
```javascript // 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
```javascript 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
```javascript // 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
```javascript 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
```javascript // 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.