Learnwizy Technologies Logo

Learnwizy Technologies

Class 34: Advanced Backend Topics - Error Handling & Logging

Building robust backend applications requires more than just implementing features. It demands careful consideration of how errors are handled and how application behavior is logged. Today, we'll dive into advanced error handling patterns in Express.js and introduce effective logging strategies. We'll also briefly discuss API documentation and environment configuration.


Centralized Error Handling in Express

In a production application, you don't want to scatter try...catch blocks everywhere or send inconsistent error responses. Centralized error handling provides a single, consistent way to manage errors across your API.

Handling 404 Not Found errors:

Any request that doesn't match a defined route will fall through to this middleware.

// server.js (main file, place this AFTER all your routes)

// Catch-all for 404 Not Found
app.use((req, res, next) => {
    res.status(404).json({
        success: false,
        message: `The resource at ${req.originalUrl} was not found.`
    });
});

Creating Custom Error Classes:

To provide more specific error types and messages, you can create custom error classes that extend Node.js's built-in `Error` class.

// utils/ApiError.js
class ApiError extends Error {
  constructor(message, statusCode, errors = []) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true; // Operational errors are expected (e.g., validation)
    this.errors = errors; // Array for validation errors
    Error.captureStackTrace(this, this.constructor); // Captures stack trace
  }
}

module.exports = ApiError;

Centralized Error Handling Middleware:

This middleware will catch all errors passed to next(err) and send a consistent JSON response.

// middleware/errorHandler.js
const ApiError = require('../utils/ApiError');

const errorHandler = (err, req, res, next) => {
  let error = { ...err }; // Copy the error object
  error.message = err.message;

  // Log the error for debugging (you'll replace this with a logger later)
  console.error(err.stack);

  // Mongoose Bad ObjectId
  if (err.name === 'CastError') {
    const message = `Resource not found with id of ${err.value}`;
    error = new ApiError(message, 404);
  }

  // Mongoose Duplicate Key (e.g., unique email)
  if (err.code === 11000) {
    const message = `Duplicate field value entered: ${Object.keys(err.keyValue)}`;
    error = new ApiError(message, 400);
  }

  // Mongoose Validation Error
  if (err.name === 'ValidationError') {
    const messages = Object.values(err.errors).map(val => val.message);
    error = new ApiError('Validation failed', 400, messages);
  }

  // If the error is not an operational error (e.g., programming error),
  // send a generic 500 message to avoid leaking sensitive info.
  if (!error.isOperational) {
    error.statusCode = 500;
    error.message = 'Something went very wrong!';
  }

  res.status(error.statusCode || 500).json({
    success: false,
    status: error.status || 'error',
    message: error.message || 'Server Error',
    errors: error.errors || []
  });
};

module.exports = errorHandler;

Using in server.js:

// server.js
// ... (imports) ...
const ApiError = require('./utils/ApiError');
const errorHandler = require('./middleware/errorHandler');

// ... (connectDB, app.use(express.json()), routes) ...

// Example route using custom error
app.get('/api/books-fail/:id', async (req, res, next) => {
  try {
    const book = await Book.findById(req.params.id);
    if (!book) {
      // Pass the custom error to the error handling middleware
      return next(new ApiError(`Book not found with ID ${req.params.id}`, 404));
    }
    res.status(200).json(book);
  } catch (error) {
    next(error); // Pass any other errors (e.g., CastError for invalid ID format)
  }
});

// Catch-all for 404 Not Found (must be before the centralized error handler)
app.use((req, res, next) => {
    res.status(404).json({
        success: false,
        message: `The resource at ${req.originalUrl} was not found.`
    });
});

// Centralized Error Handling Middleware (MUST be the last middleware)
app.use(errorHandler);

// ... app.listen() ...

This pattern makes your API's error responses consistent and easier to debug.


Logging in Node.js Applications

Logging is essential for monitoring, debugging, and auditing your application in production.


Introduction to API Documentation

API documentation is crucial for developers (both frontend and other backend teams) to understand how to use your API.


Environment Configuration (Beyond .env)

While .env files are great for local development, you'll need more robust solutions for managing configurations across different environments (development, testing, production).

Mastering error handling, logging, and proper configuration is what separates a good backend developer from a great one. These practices ensure your applications are robust, maintainable, and observable in production. In the next section, we'll shift to deployment and other key topics.