Error Handling and Logging in Node.js

Error handling and logging are foundational practices for maintaining stability, debugging issues, and understanding the behavior of Node.js applications. Node.js provides built-in error handling capabilities and allows developers to integrate logging systems to capture, log, and analyze errors in both development and production environments.

Introduction to Error Handling in Node.js

Error handling is a strategy for managing unexpected conditions in code execution. It helps prevent application crashes, enhances the user experience by providing informative error messages, and allows developers to log errors for debugging and monitoring.

Types of Errors in Node.js

Node.js errors can be broadly categorized as:

  • Synchronous Errors: These occur during function execution and can be caught using try...catch blocks.
  • Asynchronous Errors: These happen in asynchronous functions or callback-based operations.
  • Operational Errors: Expected errors that occur due to environmental factors, like network issues or file not found.
  • Programming Errors: Bugs in the code, such as syntax errors, logic errors, or null pointer exceptions.

Understanding these types helps developers apply the most suitable handling strategy.

Basic Error Handling Techniques

Handling Synchronous Errors

Using try...catch is the primary method for handling synchronous errors.

				
					try {
  let result = riskyOperation();
  console.log(result);
} catch (error) {
  console.error("An error occurred:", error.message);
}

				
			

Output: If riskyOperation throws an error, it will be caught, and an error message will be displayed instead of crashing the program.

Handling Asynchronous Errors

Asynchronous operations often involve callbacks, async/await, or promises.

  • Callback Pattern: Handle errors by including an error parameter in the callback.

				
					const fs = require('fs');

fs.readFile('nonexistent.txt', 'utf-8', (err, data) => {
  if (err) {
    console.error("File read error:", err.message);
    return;
  }
  console.log("File content:", data);
});

				
			
  • Using Promises with .catch(): Promises allow handling errors at the end of the chain with .catch().
				
					fetch('https://invalid-url')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error("Fetch error:", error.message));

				
			
  • Using async/await with try...catch:
				
					async function fetchData() {
  try {
    const response = await fetch('https://invalid-url');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Async error:", error.message);
  }
}

				
			

Advanced Error Handling Techniques

Custom Error Classes

Creating custom error classes helps in categorizing errors and improving error messages.

				
					class NotFoundError extends Error {
  constructor(message) {
    super(message);
    this.name = "NotFoundError";
    this.statusCode = 404;
  }
}

try {
  throw new NotFoundError("Data not found.");
} catch (error) {
  console.error(`${error.name} (${error.statusCode}): ${error.message}`);
}

				
			

Output:

				
					NotFoundError (404): Data not found.

				
			

Global Error Handling

In Node.js, you can set global error handlers to catch unhandled errors.

  • Handling Uncaught Exceptions:

				
					process.on('uncaughtException', (error) => {
  console.error("Uncaught exception:", error.message);
  process.exit(1); // optional
});

				
			
  • Handling Unhandled Promise Rejections:
				
					process.on('unhandledRejection', (reason, promise) => {
  console.error("Unhandled rejection:", reason);
});

				
			

Logging in Node.js

Logging involves capturing application events and errors for analysis. Logging in development differs from production:

  • Development Logging: Includes detailed logs for debugging.
  • Production Logging: Focuses on minimal logs, prioritizing error and performance logs.

Integrating Logging Libraries in Node.js

Popular Node.js logging libraries include:

  • Winston: A highly configurable logging library with support for multiple transports.
  • Morgan: A middleware used specifically for HTTP request logging in Express applications.
  • Pino: A fast JSON logger for Node.js, suitable for production environments.

Using Winston for Logging

1. Install Winston:

				
					npm install winston

				
			

2. Basic Setup:

				
					const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'error.log', level: 'error' })
  ]
});

logger.info("This is an info message.");
logger.error("This is an error message.");

				
			

3. Logging with Winston:

				
					logger.info("User accessed homepage.");
logger.warn("User tried to access deprecated endpoint.");
logger.error("Database connection failed.");

				
			

Output:

				
					{"level":"info","message":"User accessed homepage."}
{"level":"warn","message":"User tried to access deprecated endpoint."}
{"level":"error","message":"Database connection failed."}

				
			

Example: Error Handling and Logging Setup in Node.js

Step 1: Create a Basic Express Server

				
					const express = require('express');
const winston = require('winston');

const app = express();
const port = 3000;

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.simple(),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'application.log' })
  ]
});

app.get('/', (req, res) => {
  logger.info("Homepage accessed.");
  res.send("Welcome to the homepage.");
});

app.get('/error', (req, res) => {
  try {
    throw new Error("Intentional error");
  } catch (error) {
    logger.error(error.message);
    res.status(500).send("Something went wrong.");
  }
});

app.listen(port, () => {
  logger.info(`Server running on http://localhost:${port}`);
});

				
			

Explanation:

  • The server logs messages to the console and a file.
  • Visiting / logs an info message.
  • Visiting /error logs an error message due to the intentional error.

Output: If we access /error, the console and log file display:

				
					{"level":"info","message":"Server running on http://localhost:3000"}
{"level":"info","message":"Homepage accessed."}
{"level":"error","message":"Intentional error"}

				
			

Error handling and logging are essential components in building reliable Node.js applications. Proper error handling ensures applications remain stable and recover from unexpected issues. Implementing logging with tools like Winston provides insight into application health and aids in debugging and analysis. Happy Coding!❤️

Table of Contents