@idevconn/nestjs-resilience
v0.3.0
Published
Never-crash-on-missing-config helpers for NestJS microservices (env banners, strategy-with-fallback, transport resolution, retrying bootstrap, HMAC signing) plus framework-agnostic resilience primitives — timeout, retry with backoff, circuit breaker, bulk
Readme
nestjs-resilience
Never-crash-on-missing-config helpers for NestJS microservices, plus framework-agnostic resilience primitives (timeout/retry/circuit-breaker/ bulkhead) for the RPC calls between them.
The config-boot side: extracted from a project-wide pattern where a
microservice with a strategy provider (auth, db, mail, storage, ...) selected
at runtime by env vars should never crash the whole app because a .env is
incomplete — in dev it should log a loud, boxed warning and fall back to an
in-memory fake; in prod it should fail fast with the same clear banner
instead of a bare stack trace.
The RPC-resilience side: bootstrap-time retry doesn't help once a service is
up and a gateway is calling it — those ClientProxy.send() calls (or
outbound HTTP to a SaaS) need their own timeout, retry, circuit breaker and
concurrency cap, composable via composeResilience / resilientSend.
What's in here
env-banner—missingEnv,formatEnvBanner,requireEnv,formatGatewayBanner: build the boxed banner and read env vars that must fail loudly when missing.hmac—signHmac/verifyHmac: deterministic HMAC-SHA256 over a JSON payload, for signing inter-service requests.transport—buildTransport/buildTransportMS: resolve a NestJSClientOptions/MicroserviceOptionsfrom${PREFIX}_TRANSPORTand its related env vars (tcp / redis / nats / mqtt / rmq / kafka), each transport configured for infinite reconnect after a broker drop.strategy-fallback—buildStrategyWithFallback: build a concrete strategy or fall back to a fake, honoring the dev-warns / prod-throws rule.bootstrap-microservice—bootstrapMicroservice: boots a microservice and retrieslisten()when a broker (redis/nats) isn't up yet on boot, instead of crashing — NestJS'sServerRedis/ServerNatsreject the initial connect regardless of the transport's own retry options.
Resilience primitives for actual RPC calls (gateway→MS ClientProxy.send(),
outbound HTTP to SaaS) — framework-agnostic, no @nestjs/* import:
timeout—withTimeout: races a call against amstimer, rejects withTimeoutError(carryingtimeoutMs) if it fires first.retry—withRetry: exponential backoff with ±20% jitter, anisRetryablepredicate to skip non-transient errors, rejects with the last real error on exhaustion (not a generic wrapper).circuit-breaker—CircuitBreaker/CircuitBreakerRegistry: closed/open/half-open state machine per key (one instance per downstream service/provider),CircuitOpenErroron fail-fast.bulkhead—Bulkhead: caps concurrent calls atmaxConcurrent, queues the rest (bounded bymaxQueue),BulkheadRejectedErrorwhen the queue overflows.resilience—composeResilience: wires the four primitives together in a fixed order —bulkhead → circuit breaker → retry → timeout, timeout applied per retry attempt, not to the whole retry series. See the doc-comment oncomposeResiliencefor why that order matters.nest-resilient-client—resilientSend: wrapsClientProxy.send(pattern, data)withcomposeResilience, closing the gap where gateway→microservice RPC calls had no timeout/retry/circuit breaker of their own. The only file in this package that imports@nestjs/microservices/rxjs.
Install
npm install @idevconn/nestjs-resilience@nestjs/common, @nestjs/microservices (v10 or v11) and rxjs are peer
dependencies.
Usage
import {
buildTransport,
bootstrapMicroservice,
buildStrategyWithFallback,
signHmac,
verifyHmac,
} from '@idevconn/nestjs-resilience';
// main.ts
await bootstrapMicroservice(
'AUTH',
() => NestFactory.createMicroservice(AppModule, { ...buildTransport('AUTH') }),
logger,
);
// app.module.ts strategy factory
const authStrategy = buildStrategyWithFallback({
service: 'auth MS',
provider: process.env.AUTH_PROVIDER,
requiredEnv: ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY'],
cfg: configService,
envPath: 'apps/microservices/auth/.env',
build: () => new SupabaseAuthStrategy(),
fake: () => new FakeAuthStrategy(),
});Resilient gateway→microservice RPC call, one CircuitBreaker shared per
downstream service via the registry:
import { CircuitBreakerRegistry, resilientSend } from '@idevconn/nestjs-resilience';
const breakers = new CircuitBreakerRegistry({ failureThreshold: 5, resetTimeoutMs: 30_000 });
const user = await resilientSend<User>(authClient, 'get_user', { id }, {
timeout: { ms: 2000 },
retry: { maxAttempts: 3, baseDelayMs: 200, isRetryable: (err) => !(err instanceof BadRequestError) },
circuitBreaker: breakers.get('auth'),
});Development
npm install
npm test
npm run build