CircuitBreaker middlewares
Initial configuration
To begin using the circuit-breaker middlewares, you'll need to create and configure a CircuitBreakerFactoryResolver instance:
import { CircuitBreakerFactoryResolver } from "eridu-tech/circuit-breaker";
import { DatabaseCircuitBreakerAdapter } from "eridu-tech/circuit-breaker/database-circuit-breaker-adapter";
import { MemoryCircuitBreakerStorageAdapter } from "eridu-tech/circuit-breaker/memory-circuit-breaker-storage-adapter";
import { RedisCircuitBreakerAdapter } from "eridu-tech/circuit-breaker/redis-circuit-breaker-adapter";
import { SuperJsonSerde } from "eridu-tech/serde/super-json-serde";
import { Redis } from "ioredis";
const serde = new SuperJsonSerde();
export const circuitBreakerFactoryResolver = new CircuitBreakerFactoryResolver({
serde,
adapters: {
memory: new DatabaseCircuitBreakerAdapter({
adapter: new MemoryCircuitBreakerStorageAdapter(),
}),
redis: new RedisCircuitBreakerAdapter({
database: new Redis("YOUR_REDIS_CONNECTION_STRING"),
}),
},
defaultAdapter: "memory",
});
await circuitBreakerFactoryResolver.init();
withCircuitBreakerFactory middleware
The CircuitBreaker middleware wraps function calls with a circuit-breaker, providing fault tolerance for distributed systems. Each unique key (derived from the function's arguments) gets its own circuit instance. When the circuit is open, the wrapped function is not called and an error is thrown instead, preventing cascading failures.
Usage
import { withCircuitBreakerFactory } from "eridu-tech/circuit-breaker/middlewares";
import { use } from "eridu-tech/middleware";
import { circuitBreakerFactoryResolver } from "./circuit-breaker.js";
const withCircuitBreaker = withCircuitBreakerFactory(
circuitBreakerFactoryResolver,
);
const callExternalApi = async (endpoint: string): Promise<unknown> => {
const response = await fetch(`https://api.example.com/${endpoint}`);
return response.json();
};
// Wrap with circuit-breaker using the default adapter (`memory`)
const protectedCall = use(
callExternalApi,
withCircuitBreaker({
key: ([endpoint]) => `api:${endpoint}`,
}),
);
// Wrap with circuit-breaker using a specific adapter (`redis`)
const protectedCallOnRedis = use(
callExternalApi,
withCircuitBreaker.use("redis")({
key: ([endpoint]) => `api:${endpoint}`,
}),
);
await protectedCall("users"); // Succeeds or opens the circuit on repeated failures
await protectedCallOnRedis("users"); // Uses the Redis adapter
Calling withCircuitBreaker() directly uses the resolver's default adapter (memory). Use withCircuitBreaker.use("redis") to run the wrapped function through a specific registered adapter.
Here is a complete list of settings for the withCircuitBreaker function.
Settings
| Option | Type | Description |
|---|---|---|
key | Invocable<TParameters, string> | A function that produces a unique identifier for the circuit from the wrapped function's arguments. Each unique key gets its own circuit state |
errorPolicy | ErrorPolicy | Determines which errors count toward opening the circuit. Defaults to treating all errors as failures |
trigger | CircuitBreakerTrigger | Optional custom trigger that determines when the circuit should open. Defaults to the built-in trigger |
slowCallTime | ITimeSpan | Optional duration above which a call is considered slow. Exceeding it counts toward opening the circuit (if configured in the trigger) |
withHttpCircuitBreakerError middleware
The withHttpCircuitBreakerError middleware converts every circuit-breaker error thrown by the wrapped endpoint or downstream middleware into an HttpError, so the request fails with a meaningful status code instead of a generic 500. Errors that are not circuit-breaker errors are rethrown untouched, and the original error is preserved as the cause.
Usage
import { OpenCircuitBreakerError } from "eridu-tech/circuit-breaker/contracts";
import { withHttpCircuitBreakerError } from "eridu-tech/circuit-breaker/middlewares";
import { HttpRouter, defaultHttpRouterAdapter } from "eridu-tech/http-router";
const router = new HttpRouter({
router: defaultHttpRouterAdapter(),
});
router.use(withHttpCircuitBreakerError());
router.endpoint({
url: "/payments/1",
method: ["POST"],
handler: async () => {
// A 503 response when the circuit is open
throw OpenCircuitBreakerError.create("payments");
},
});
Status codes
| Errors | Status |
|---|---|
OpenCircuitBreakerError, IsolatedCircuitBreakerError | 503 |
UnableToGetStateCircuitBreakerError, UnableToUpdateStateCircuitBreakerError, UnableToIsolateCircuitBreakerError, UnableToTrackFailureCircuitBreakerError, UnableToTrackSuccessCircuitBreakerError, UnableToResetCircuitBreakerError | 503 |
Every circuit-breaker error means the circuit is not accepting calls or the underlying adapter failed, so they all map to 503 and the same request can be retried later. The original error is preserved as the cause, and no identifiers are exposed in the response body.
Further information
For further information refer to eridu-tech/circuit-breaker API docs.