Skip to content

Circuit Breaker Plugin

A distributed circuit breaker (closed / open / half-open) that stops calls to a failing dependency, gives it time to recover, and probes before restoring traffic — consistently across all application instances via Redis.

Overview

When a downstream dependency (payment gateway, third-party API, another service) starts failing, hammering it makes things worse and ties up your own resources. The circuit breaker counts failures and, once a threshold is crossed, opens — rejecting calls immediately instead of waiting on timeouts. After a cooldown it moves to half-open, letting a limited number of probe calls through; if they succeed it closes again.

ChallengeWithout a breakerWith the Circuit Breaker Plugin
Failing dependencyEvery call waits for a timeoutCalls fail fast while OPEN
RecoveryThundering herd on recoveryControlled probes (HALF_OPEN)
Multi-instanceEach instance decides aloneShared state across instances via Redis

Key Features

  • Distributed state — the breaker state lives in Redis, so all instances agree.
  • Pure, time-injected core — the state machine (CircuitBreakerState) takes an explicit now; no hidden Date.now(), fully deterministic and unit-testable.
  • Atomic transitions — state changes run in Lua for correctness under concurrency.
  • Works anywhere — the @WithCircuitBreaker decorator wraps any Injectable method (proxy-based, not a controller guard).
  • Fallbacks — return a cached/default value instead of throwing while OPEN.
  • fail-open / fail-closed — choose availability or strictness when the state store itself is unavailable.

Installation

bash
npm install @nestjs-redisx/core @nestjs-redisx/circuit-breaker ioredis
bash
npm install @nestjs-redisx/core @nestjs-redisx/circuit-breaker redis

Basic Configuration

typescript
import { Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-redisx/core';
import { CircuitBreakerPlugin } from '@nestjs-redisx/circuit-breaker';

@Module({
  imports: [
    RedisModule.forRoot({
      clients: {
        host: 'localhost',
        port: 6379,
      },
      plugins: [
        new CircuitBreakerPlugin({
          failureThreshold: 5, // trip after 5 failures...
          windowMs: 10000, // ...within a 10s rolling window
          openDurationMs: 30000, // stay OPEN for 30s before probing
          halfOpenMaxCalls: 1, // allow 1 probe while HALF_OPEN
          successThreshold: 1, // 1 successful probe closes the breaker
          probeTimeoutMs: 30000, // reclaim a probe slot if its outcome is never recorded
        }),
      ],
    }),
  ],
})
export class AppModule {}

Usage with the Decorator

typescript
import { Injectable } from '@nestjs/common';
import { WithCircuitBreaker } from '@nestjs-redisx/circuit-breaker';
import { PaymentGateway, UsersApi, UserCache, Charge, User } from './types';

@Injectable()
export class PaymentsService {
  constructor(
    private readonly gateway: PaymentGateway,
    private readonly usersApi: UsersApi,
    private readonly userCache: UserCache,
  ) {}

  // Trips after repeated failures; throws CircuitBreakerOpenError while OPEN.
  @WithCircuitBreaker({ key: 'stripe', failureThreshold: 5, openDurationMs: 30000 })
  async charge(charge: Charge): Promise<{ ok: boolean }> {
    return this.gateway.charge(charge);
  }

  // Key interpolated from the first argument; falls back to cache when OPEN.
  @WithCircuitBreaker({
    key: 'users-api:{0}',
    fallback: (id: string) => ({ id, name: 'cached' }),
  })
  async getUser(id: string): Promise<User> {
    return this.usersApi.getUser(id);
  }

  // Skip execution (resolve to undefined) instead of throwing while OPEN.
  @WithCircuitBreaker({ key: 'users-api', onOpen: 'skip' })
  async warmCache(id: string): Promise<User | null> {
    return this.userCache.get(id);
  }

  // Bypass the breaker entirely for trusted/internal calls via skip().
  @WithCircuitBreaker({
    key: 'users-api',
    skip: (id: string, internal?: boolean) => internal === true,
  })
  async getUserMaybeInternal(id: string, internal?: boolean): Promise<User> {
    return this.usersApi.getUser(id);
  }
}

States

Documentation

TopicDescription
Core ConceptsStates and when to use a breaker
ConfigurationOptions and defaults
@WithCircuitBreaker DecoratorMethod-level breaking
Service APIProgrammatic execute / manual recording
AlgorithmStates, window, cooldown, probes
MonitoringObserving circuit state
RecipesFallbacks and patterns
TestingTesting breaker-guarded code
TroubleshootingDebugging common issues

Released under the MIT License.