Connecting Services — Event Bridges

Queue Bridges

Default, Redis, NATS — reliable work distribution

While event bridges handle push-based messaging (commands, subscriptions, events), queue bridges handle pull-based background work. They provide job queues with explicit leases, heartbeats, retries, and dead-letter routing — essential for reliable asynchronous processing.

Why queue bridges exist

Event bridge subscriptions are fire-and-forget push deliveries: the broker pushes the message to the handler and, if the handler crashes mid-execution, the message may be lost. For most real-time service-to-service traffic that is acceptable.

Queue bridges solve a different problem. They use a lease model: a worker claims a job for a fixed window (visibilityTimeoutMs). If the worker does not heartbeat or acknowledge within that window, the job becomes visible again and another worker can claim it. This guarantees at-least-once delivery even if a pod is killed mid-job.

The key differences at a glance:

Event bridge subscriptionQueue bridge
Delivery modelPush (broker pushes to handler)Pull (worker claims from queue)
Job persistenceNo (lost if broker restarts without durability)Yes (Redis or NATS JetStream)
Lease / visibility timeoutNoYes
Heartbeat extensionNoYes
Dead-letter queueBridge-dependentYes
Backlog visibilityNoYes
Message replayNoNATS JetStream only

Use a queue bridge when job loss on failure is unacceptable, when jobs run longer than a message timeout, or when you need operator-visible backlogs and retry counts.

Queue bridges are independent of your event bridge choice. A service can use NatsBridge for event traffic and RedisQueueBridge for worker queues at the same time. See the Event Bridges page for a pairing guide.

Available queue bridges

BridgePackageBackendBest for
DefaultQueueBridge@purista/coreIn-memoryLocal development, testing
RedisQueueBridge@purista/redis-queue-bridgeRedisProduction pull-based workloads
NatsQueueBridge@purista/nats-queue-bridgeNATS JetStreamNATS-first platforms

The DefaultQueueBridge

For local development and testing:

import { DefaultQueueBridge } from '@purista/core'

const queueBridge = new DefaultQueueBridge()

Jobs are stored in-memory and lost on restart. Use only for development.

Redis Queue Bridge

For production workloads:

import { RedisQueueBridge } from '@purista/redis-queue-bridge'

const queueBridge = new RedisQueueBridge({
  url: process.env.REDIS_URL,
})

Redis provides:

  • Persistent job storage
  • Visibility timeouts (leases)
  • Heartbeat support
  • Dead-letter routing
  • Backlog metrics

NATS JetStream Queue Bridge

For NATS-first platforms:

import { NatsQueueBridge } from '@purista/nats-queue-bridge'

const queueBridge = new NatsQueueBridge({
  url: process.env.NATS_URL,
})

NATS JetStream provides:

  • Durable streams
  • Consumer groups
  • Message replay
  • At-least-once delivery

Service integration

Pass the queue bridge when creating service instances:

const userService = await userServiceV1Service.getInstance(eventBridge, {
  queueBridge,
  resources,
})

If you skip queueBridge, PURISTA injects the in-memory default automatically — convenient for tests, but not for production.

Queue lifecycle

flowchart LR
    A[Enqueue] --> B[Available]
    B --> C[Lease to Worker]
    C --> D{Success?}
    D -->|Yes| E[Complete]
    D -->|No| F{Retries Left?}
    F -->|Yes| G[Delay & Retry]
    G --> B
    F -->|No| H[Dead Letter]
  1. Enqueue — a command or subscription adds a job
  2. Available — job is visible to workers
  3. Lease — worker claims the job for visibilityTimeoutMs
  4. Process — worker executes the handler
  5. Heartbeat — worker extends lease for long-running jobs
  6. Complete / Retry / Dead-letter — based on success or failure

Comparing queue bridges

FeatureDefaultRedisNATS JetStream
Persistence❌ (in-memory)
Leases
Heartbeats
Dead-letter
Delayed jobs
Backlog metrics
Message replay

When to use queue bridges

  • Background jobs that outlive the request that created them
  • Scheduled/delayed work (cron-like tasks)
  • Long-running operations with progress tracking
  • Work that needs operator visibility (backlogs, retries)
  • Batch processing with controlled concurrency

When NOT to use queue bridges

  • Real-time request/response — use event bridges with commands
  • Fire-and-forget events — use subscriptions
  • Simple in-process async work — use Node.js promises

Common pitfalls

  • Using DefaultQueueBridge in production. Jobs are lost on restart.
  • Not configuring heartbeat intervals. Long jobs fail when leases expire.
  • Ignoring dead-letter queues. Failed jobs accumulate silently.
  • Mixing queue and event bridge semantics. Queues are pull-based with leases; subscriptions are push-based.

Checklist

  • Production uses a persistent queue bridge (Redis or NATS)
  • Lease timeout matches expected job duration
  • Heartbeat interval is configured for long-running jobs
  • Dead-letter queue is monitored
  • Backlog metrics are visible to operators
  • Retry policies are tested against the actual bridge

Related

Read Next
Schedule → Event → Queue → Result

from Enterprise Patterns