Implement Circuit Breaker Pattern in Node.js

In microservices architectures, a failure in a downstream dependency can cascade and cause your entire application to crash. This article provides a direct, practical guide on how to implement the circuit breaker pattern in a Node.js service using the popular opossum library. You will learn the core concepts of the circuit breaker states (Closed, Open, and Half-Open) and how to configure a resilient system with a step-by-step code implementation.

Understanding the Circuit Breaker States

The circuit breaker pattern prevents an application from repeatedly trying to execute an operation that is highly likely to fail. It operates in three states:

Step-by-Step Implementation in Node.js

The standard and production-ready library for implementing circuit breakers in Node.js is opossum.

1. Install the Library

First, initialize your project and install the opossum package.

npm install opossum

2. Define the Downstream Operation

Create a simulated downstream service call. In a real-world application, this would be an HTTP request (using axios or fetch) or a database query.

// service.js
// A simulated unstable downstream HTTP request
async function callDownstreamDependency() {
  // Simulating a 50% failure rate
  if (Math.random() > 0.5) {
    throw new Error("Downstream service is unavailable");
  }
  return { status: "Success", data: "Payload from downstream" };
}

3. Implement and Configure the Circuit Breaker

Now, wrap the downstream function with opossum and define the configuration parameters.

const CircuitBreaker = require('opossum');

// Configuration options
const options = {
  timeout: 3000, // If our function takes longer than 3 seconds, trigger a failure
  errorThresholdPercentage: 50, // Trip the circuit if 50% of requests fail
  resetTimeout: 10000 // Wait 10 seconds before attempting to transition from Open to Half-Open
};

// Instantiate the circuit breaker
const breaker = new CircuitBreaker(callDownstreamDependency, options);

// Define fallback behavior when the circuit is open or failing
breaker.fallback(() => {
  return { status: "Fallback", data: "Default cached data" };
});

4. Monitor Circuit Events

You can listen to specific events emitted by the circuit breaker to log state changes, which is vital for observability and alerting.

breaker.on('fire', () => console.log('Circuit executed.'));
breaker.on('reject', () => console.warn('Circuit is OPEN. Request rejected.'));
breaker.on('timeout', () => console.warn('Downstream request timed out.'));
breaker.on('open', () => console.warn('Circuit state transitioned to OPEN.'));
breaker.on('close', () => console.log('Circuit state transitioned to CLOSED.'));
breaker.on('halfOpen', () => console.log('Circuit state transitioned to HALF-OPEN.'));

5. Execute the Service Call

Integrate the circuit breaker into your application route or controller. Instead of calling your downstream function directly, execute it via the breaker’s fire() method.

async function handleUserRequest(req, res) {
  try {
    const result = await breaker.fire();
    res.status(200).json(result);
  } catch (error) {
    // If fallback is not defined, error is handled here
    res.status(500).json({ error: error.message });
  }
}

Best Practices for Production