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

```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.

#Node.js#Error Handling#Best Practices#Intermediate