Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Use BullMQ’s QueueEvents when one or more services need to observe job lifecycle events across every worker handling a queue. Workers distribute jobs among themselves; they do not each receive a copy. For cross-worker visibility without adding another broker, give each observing service its own QueueEvents instance connected to Redis.

What “fan-out” means in BullMQ

BullMQ separates job processing from event observation. A Worker consumes and processes jobs from a shared queue. Multiple workers can share that work, but a job is not copied to every worker. A worker’s ordinary event listeners are local to that worker’s process, so they are not a queue-wide feed.

QueueEvents provides that queue-wide observation point. It is implemented with Redis Streams, which BullMQ documents as providing delivery guarantees across listener disconnections that standard pub/sub does not. Each independent service that needs to observe events should create its own QueueEvents instance and register handlers for the event types it needs. Within one process, an instance can dispatch events to several callbacks.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set up a QueueEvents listener

  1. Create the listener: import QueueEvents from bullmq and construct it with the queue name and the application’s Redis connection.
  2. Register event handlers: listen for the lifecycle events the service needs, such as completed, failed, or progress.
  3. Wait until it is ready: call waitUntilReady() before relying on the listener.
  4. Close it at shutdown: call close() so the listener releases its Redis connection.
import { QueueEvents } from 'bullmq';

const queueEvents = new QueueEvents('orders', { connection });

queueEvents.on('completed', ({ jobId, returnvalue }) => {
  notifyConsumers({ type: 'completed', jobId, returnvalue });
});

queueEvents.on('failed', ({ jobId, failedReason }) => {
  notifyConsumers({ type: 'failed', jobId, failedReason });
});

await queueEvents.waitUntilReady();

// During application shutdown:
await queueEvents.close();

This is an illustrative TypeScript pattern based on BullMQ’s documented API, not a tested implementation. Adapt the connection configuration and the event fields to the installed BullMQ version and your application.

Choose the right event scope and payload

Approach What it observes What the handler receives When it fits
Worker event listener Events local to the worker process that completed the job Worker-local event data, which can include the job object depending on the event Logic that belongs to that worker’s own processing lifecycle
QueueEvents Lifecycle events at queue scope, across workers Compact event fields, commonly including jobId and fields such as returnvalue, failedReason, or progress data; not a hydrated Job object Dashboards, notifications, or other services that need queue-wide visibility

Because QueueEvents payloads are lightweight, retrieve the job by its ID only when the observer genuinely needs the full object. BullMQ’s quick start describes fetching a job with Job.fromId as an option. Avoid assuming that an event contains every property available on a Job.

Account for retention and Redis

BullMQ automatically trims the QueueEvents stream to approximately 10,000 events by default, according to its current Events guide, accessed in 2026. The maximum can be changed with streams.events.maxLen. Choose a retention limit with expected event volume and plausible listener downtime in mind: a stream trimmed while a consumer is disconnected cannot serve as an unlimited historical event archive.

Redis is still required. BullMQ stores jobs in Redis, and its quick start requires a running Redis service. QueueEvents avoids introducing a separate broker for this observation pattern; it does not remove BullMQ’s Redis dependency.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use custom events when lifecycle events are not enough

If observers need application-defined event names rather than job lifecycle changes, BullMQ documents QueueEventsProducer for publishing custom events that QueueEvents consumers can subscribe to. That extends the event vocabulary; it does not change the distinction between observing an event and distributing a separate copy of each job to every consumer.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Handle errors and distinguish observation from business delivery

Attach appropriate error handlers to workers and listeners. BullMQ warns that an unhandled worker error can stop processing. Treat queue events as lifecycle observations for services such as dashboards or notifications; BullMQ’s documentation does not present QueueEvents as a replacement for a separately designed durable business-event system.

If every independent consumer must process its own copy of every work item, QueueEvents is not that mechanism. Design explicit job fan-out so each consumer has work to process; use QueueEvents when the requirement is to observe what happened to jobs across workers.

Documentation

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.