Home/Docs/circuit-breaker
CB

circuit-breaker

v0.3.1

@backendkit-labs/circuit-breaker

Prevent cascading failures with intelligent error classification.

Overview#

The circuit breaker pattern prevents cascading failures: when a downstream service starts failing repeatedly, the breaker opens — subsequent calls short-circuit immediately without hitting the service — then after a cooldown it enters a half-open state to probe recovery.

The critical differentiator is error classification. Traditional implementations (like opossum) treat all errors equally. In practice a card_declined from Stripe is a business error — the service worked correctly — while a connection timeout is an infrastructure failure. The isFailure callback lets you tell them apart: only infra failures count against the breaker, preventing phantom opens caused by expected business rejections.

Failure rate is measured over a configurable sliding window of recent calls (not a fixed time interval), giving a more accurate picture of current health. Built-in retry with exponential backoff + jitter is available for transient errors before the breaker is involved.

States & Lifecycle#

Closed

Normal operation. All calls go through. Failure rate is tracked in the sliding window.

Open

Failure threshold exceeded. Calls return circuit_open immediately without hitting downstream.

Half-Open

Cooldown elapsed. One probe call is allowed. Success → Closed, failure → Open again.

Configuration#

PropTypeDescription
namestringUnique identifier used in logs and metrics.
failureThresholdnumberFailure rate (0–100 %) that trips the breaker open.
sampleSizenumberSliding window size — number of recent calls over which failure rate is measured.
cooldownMsnumberMilliseconds to stay OPEN before attempting a single half-open probe.
isFailure(err) => booleanReturns true for infrastructure errors that count against the breaker. Return false for business errors (4xx, domain rejections) to prevent phantom opens.
onStateChange(prev, next, metrics) => voidCalled on every state transition with a metrics snapshot. Use for alerting or logging.
retry.attemptsnumberNumber of retry attempts with exponential backoff + jitter before the breaker counts a failure.
retry.baseDelayMsnumberInitial retry delay in ms. Doubles each attempt.

NestJS Integration#

Register the module once, then use the decorator on any service method. The CircuitBreakerRegistry provides pre-configured factories so you do not need to tune thresholds for common patterns.

app.module.ts
import { CircuitBreakerModule } from '@backendkit-labs/circuit-breaker/nestjs';

@Module({
  imports: [CircuitBreakerModule.forRoot()],
})
export class AppModule {}

// payment.service.ts
@Injectable()
export class PaymentService {
  @WithCircuitBreaker({ name: 'stripe', failureThreshold: 40, cooldownMs: 30_000 })
  async charge(dto: ChargeDto): Promise<Result<PaymentIntent, ChargeError>> {
    return this.stripeGateway.createCharge(dto);
    // result.error.type === 'circuit_open' when the breaker is open
  }
}

CircuitBreakerRegistry

Instead of tuning individual instances, the registry provides three pre-configured factory methods covering the most common scenarios:

PropTypeDescription
getForHttpExternal(name)CircuitBreakerFor calls to external HTTP APIs. 8-call window, 10-call sample, 5 s timeout.
getForService(name)CircuitBreakerFor internal microservice calls. 20-call window, 20-call sample, 10 s timeout.
getForDatabase(name)CircuitBreakerFor database connections. 15-call window, 15-call sample, 3 s timeout.
registry.example.ts
import { CircuitBreakerRegistry } from '@backendkit-labs/circuit-breaker';

const registry = new CircuitBreakerRegistry();

// Pre-tuned for external HTTP — no manual threshold guessing
const stripeBreaker  = registry.getForHttpExternal('stripe');
const dbBreaker      = registry.getForDatabase('postgres');
const orderBreaker   = registry.getForService('order-service');

Examples#

From basic to production-grade — copy and adapt.

stripe.service.ts
import { CircuitBreaker } from '@backendkit-labs/circuit-breaker';

const cb = new CircuitBreaker({
  name: 'stripe',
  failureThreshold: 40,  // open after 40 % failure rate
  sampleSize: 20,        // over the last 20 calls
  cooldownMs: 30_000,    // wait 30 s before probing again
});

const result = await cb.execute(() => stripe.charges.create(dto));

if (result.ok) {
  return result.value;
} else if (result.error.type === 'circuit_open') {
  throw new ServiceUnavailableException('Payment service down');
}

⚖️ vs. Alternatives#

Comparing against opossum — the most popular and established circuit breaker library in the Node.js ecosystem.

Feature@backendkit-labs/circuit-breakeropossum (v9)
Business error classification✅ isBusinessError()❌ All errors equal
Sliding-window tracking✅ Count-based✅ Count-based
Half-open probing
getMetrics()✅ failureRate, totalCalls✅ stats object
onStateChange hook✅ + metrics snapshot✅ events
Result<T,E> integration✅ Native❌ Throws / callbacks
NestJS decorator✅ @UseCircuitBreaker❌ Manual wiring
AbortController support
Runtime dependencies0 (core)0
Weekly downloadsGrowing~2.2M

✅ Supported  ·  ❌ Not supported  ·  ⚠️ Partial / workaround needed. Download counts are approximate weekly npm averages.