BullMQ Core Cheatsheet (v5, TypeScript)

One-page lookup for the three primitives every BullMQ system is built from: Queue, Worker, Job. Cite: Queues, Workers, Jobs, Connections.

Mental model

Producer ──add()──▶ Queue (Redis list) ──pull──▶ Worker ──▶ done | failed
                          ▲
                   stored as Job objects

Connection (the one gotcha)

Always set maxRetriesPerRequest: null on the ioredis instance. BullMQ uses blocking Redis commands; without this, ioredis throws BREADONLY / Connection is closed. under failover.
import IORedis from 'ioredis';
const connection = new IORedis('redis://localhost:6379', {
  maxRetriesPerRequest: null,
});

Pass the same connection to every Queue and Worker in that process, or reuse BullMQ's default.

Queue (producer)

import { Queue } from 'bullmq';

const emailQueue = new Queue<EmailPayload>('email', { connection });

await emailQueue.add('send-welcome', { to: '[email protected]', userId: 42 });
MethodUse
add(name, data, opts?)Enqueue one job. Returns Promise<Job>.
addBulk([{name, data, opts}, ...])Enqueue many in one round-trip.
getJob(id)Inspect a job by id.
getWaiting/Active/Completed/Failed/Delayed()List jobs in each state.
obliterate()Wipe the queue entirely (no undo).

Worker (consumer)

import { Worker, Job } from 'bullmq';

new Worker<EmailPayload>('email',
  async (job: Job) => {
    await job.updateProgress(50);
    await sendEmail(job.data.to);
    return { ok: true };
  },
  { connection, concurrency: 5 },
);

Worker events (local to this worker)

worker.on('completed', (job, returnvalue) => {});
worker.on('failed',    (job, err, prev) => {});  // job may be undefined
worker.on('progress',  (job, progress) => {});
worker.on('error',     err => console.error(err)); // MANDATORY or worker stalls

Cross-worker events

import { QueueEvents } from 'bullmq';
const qe = new QueueEvents('email', { connection });
qe.on('completed', ({ jobId, returnvalue }) => {});

Job shape (inside the processor)

FieldWhat it holds
job.idUnique id (auto unless you pass one in options).
job.nameThe name given to queue.add.
job.dataTyped payload. This is your contract with the producer.
job.optsAttempts, backoff, delay, priority, repeat, jobId…
job.attemptsMadeHow many tries so far (1-based after first run).
job.progressLatest value passed to updateProgress.
job.returnvalueWhatever the processor returned.

Common add() options

await queue.add('resize', { path }, {
  attempts: 5,
  backoff: { type: 'exponential', delay: 1000 },
  delay: 60_000,        // ms before worker may pick it up
  priority: 1,          // lower = higher priority
  removeOnComplete: 1000,
  removeOnFail: 5000,
  jobId: `resize-${path}`, // makes it idempotent on retry
});

Install

npm install bullmq ioredis
# Redis ≥ 2.8.18 running on localhost:6379

Next lessons: retries & backoff, delayed/repeatable jobs, flows, rate-limiting, graceful shutdown.