| name | resilience-patterns |
| description | Circuit breaker, bulkhead, retry with jitter, graceful shutdown, health check patterns for production resilience. |
Resilience Patterns
Production-grade patterns for surviving failures without cascading.
Circuit Breaker
type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN'
class CircuitBreaker {
private state: CircuitState = 'CLOSED'
private failureCount = 0
private lastFailureTime = 0
private successCount = 0
constructor(
private readonly failureThreshold = 5,
private readonly recoveryTimeout = 30_000,
private readonly halfOpenMaxCalls = 3
) {}
async call<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
this.state = 'HALF_OPEN'
this.successCount = 0
} else {
throw new Error('Circuit breaker is OPEN — request rejected')
}
}
try {
const result = await fn()
this.onSuccess()
return result
} catch (err) {
this.onFailure()
throw err
}
}
private onSuccess(): void {
if (this.state === 'HALF_OPEN') {
this.successCount++
if (this.successCount >= this.halfOpenMaxCalls) {
this.state = 'CLOSED'
this.failureCount = 0
}
} else {
this.failureCount = 0
}
}
private onFailure(): void {
this.failureCount++
this.lastFailureTime = Date.now()
if (this.failureCount >= this.failureThreshold) {
this.state = 'OPEN'
}
}
getState(): CircuitState { return this.state }
}
const paymentBreaker = new CircuitBreaker(5, 30_000)
async function chargeUser(userId: string, amount: number) {
return paymentBreaker.call(() => paymentService.charge(userId, amount))
}
Retry with Exponential Backoff + Jitter
interface RetryOptions {
maxAttempts?: number
baseDelayMs?: number
maxDelayMs?: number
jitter?: boolean
retryIf?: (error: unknown) => boolean
}
async function withRetry<T>(
fn: () => Promise<T>,
options: RetryOptions = {}
): Promise<T> {
const {
maxAttempts = 3,
baseDelayMs = 500,
maxDelayMs = 15_000,
jitter = true,
retryIf = () => true
} = options
let lastError: unknown
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn()
} catch (err) {
lastError = err
if (attempt === maxAttempts || !retryIf(err)) throw err
const base = Math.min(baseDelayMs * Math.pow(, attempt - ), maxDelayMs)
delay = jitter ? .() * base : base
.()
( (resolve, delay))
}
}
lastError
}
(
externalApi.(),
{
: ,
: err || (err )?. >=
}
)
Bulkhead Pattern
class Bulkhead {
private activeCount = 0
private queue: Array<() => void> = []
constructor(
private maxConcurrent: number,
private maxQueueSize: number = 50
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.activeCount >= this.maxConcurrent) {
if (this.queue.length >= this.maxQueueSize) {
throw new Error('Bulkhead queue full — request rejected')
}
await new Promise<void>((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error('Bulkhead queue timeout')),
)
..( { (timeout); () })
})
}
.++
{
()
} {
.--
(.. > ) {
next = ..()!
()
}
}
}
() {
{ : ., : .. }
}
}
paymentBulkhead = (, )
emailBulkhead = (, )
dbBulkhead = (, )
Timeout Policies
function withTimeout<T>(
fn: () => Promise<T>,
timeoutMs: number,
label = 'operation'
): Promise<T> {
return Promise.race([
fn(),
new Promise<never>((_, reject) =>
setTimeout(
() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
timeoutMs
)
)
])
}
const fetchWithTimeouts = async (url: string) => {
const controller = new AbortController()
const connectTimeout = setTimeout(() => controller.abort(), 2000)
try {
const response = await fetch(url, { signal: controller.signal })
clearTimeout(connectTimeout)
( response.(), , )
} {
(connectTimeout)
}
}
Fallback Chain
async function getMarketData(id: string): Promise<Market> {
return withFallbacks([
{ name: 'primary-db', fn: () => primaryDb.market.findUnique({ where: { id } }) },
{ name: 'redis-cache', fn: () => redis.get(`market:${id}`).then(v => v ? JSON.parse(v) : null) },
{ name: 'replica-db', fn: () => replicaDb.market.findUnique({ where: { id } }) },
{ name: 'stale-cache', fn: () => staleCache.get(id) }
])
}
async function withFallbacks<T>(
strategies: Array<{ name: string; fn: () => Promise<T | null> }>
): Promise<T> {
for ( { name, fn } strategies) {
{
result = ()
(result != ) result
} (err) {
.(, (err ).)
}
}
()
}
Health Check Endpoints
import express from 'express'
const app = express()
app.get('/live', (_req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() })
})
app.get('/ready', async (_req, res) => {
const checks = await Promise.allSettled([
checkDatabase(),
checkRedis(),
checkDependencies()
])
const results = checks.map((c, i) => ({
name: ['database', 'redis', 'dependencies'][i],
status: c.status === 'fulfilled' ? 'ok' : 'fail',
error: c.status === 'rejected' ? (c.reason as Error).message : undefined
}))
const allHealthy = results.( r. === )
res.(allHealthy ? : ).({ : allHealthy ? : , : results })
})
app.(, (_req, res) => {
[dbMs, redisMs] = .([(), ()])
res.({
: ,
: process.(),
: process.(),
: { : dbMs },
: { : redisMs }
})
})
(): <> {
db.
}
(): <> {
pong = redis.()
(pong !== ) ()
}
Graceful Shutdown
let isShuttingDown = false
async function gracefulShutdown(server: http.Server): Promise<void> {
console.log('SIGTERM received, starting graceful shutdown...')
isShuttingDown = true
server.close(async () => {
console.log('HTTP server closed')
try {
await jobQueue.close()
await db.$disconnect()
await redis.quit()
console.log('Graceful shutdown complete')
process.exit(0)
} catch (err) {
console.error('Error during shutdown:', err)
process.exit(1)
}
})
setTimeout(() => {
console.error()
process.()
}, )
}
app.( {
(isShuttingDown) {
res.(, )
res.().({ : })
}
()
})
process.(, (server))
process.(, (server))
Idempotency Keys
async function processPaymentIdempotent(
idempotencyKey: string,
payload: PaymentPayload
): Promise<PaymentResult> {
const lockKey = `idempotency:${idempotencyKey}`
const existing = await redis.get(lockKey)
if (existing) {
return JSON.parse(existing) as PaymentResult
}
const result = await paymentGateway.charge(payload)
await redis.setex(lockKey, 86_400, JSON.stringify(result))
return result
}
router.post('/payments', async (req, res) => {
const key = req.headers['idempotency-key'] as string
if (!key) return res.status(400).json({ error: })
result = (key, req.)
res.({ : , : result })
})
Remember: Resilience is composed — combine circuit breaker + retry + bulkhead + timeout for defense in depth. Never apply retry without a circuit breaker, or you'll amplify load on a failing service.