mrkeyoor.com_
Sat 19 Sept 15:53 UTC
npmInfraupdated 19 Sept 2026

bullmq review

BullMQ 6.3.0 is a server-side job queue for Node.js and Bun. An application puts named jobs and JSON-like data into a queue; separate Worker processes claim them and write progress, results, retries, or failures back to Redis. It also handles delayed work, cron schedules through Job Schedulers, queue-wide rate limits, deduplication, priorities, and parent-child flows. The 6.3.0 release does not change the Node API in its release notes; its listed feature passes PostgreSQL SSL options to the Elixir client. Node users do get the stalled-job key fix from 6.2.1. Our v6.2.0 install was a 13 MB Node-only package graph with bundled TypeScript declarations.

Verdict

BullMQ 6.2.0 took 4.4 seconds and 13 MB in our sandbox, passed npm audit with 0 findings, and failed a browser bundle, which makes it a practical server-side queue for teams already prepared to operate Redis. Do not install it for a single-process timer or for Pro-only group controls.

We installed it

Lab card: what happened when we installed bullmqScreenshot of bullmq documentation
Install✓ · 4.4s13 packages on disk · 13 MB
ImportESM import works · require() works · CommonJS package
Browsern/acould not be bundled for the browser (Node-only code, most likely)
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does bullmq install cleanly?

Yes. In a fresh container with an empty cache, npm install bullmq finished in 4 seconds, leaving 13 packages and 13 MB on disk. npm audit reported no known vulnerabilities.

Can bullmq run in a browser?

Not directly: esbuild could not bundle it for the browser in our run, which normally means it depends on Node built-ins. Use it on the server, or find a browser-targeted alternative.

Does bullmq work with both ESM and CommonJS?

Yes. Both import 'bullmq' and require('bullmq') worked in Node 22 in our run. The package is published as CommonJS.

Does bullmq include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

bullmq or pg-boss: which should you use?

pg-boss: PostgreSQL is already mandatory and adding Redis solely for background jobs is undesirable. BullMQ 6.2.0 took 4.4 seconds and 13 MB in our sandbox, passed npm audit with 0 findings, and failed a browser bundle, which makes it a practical server-side queue for teams already prepared to operate Redis.

When should you not use bullmq?

One in-process timer can safely own the task. BullMQ brings a datastore, long-lived workers, shutdown handling, and stored job state that a small cron callback does not need.

API stability3/5Queue, Worker, QueueEvents, FlowProducer, processor callbacks, and job options form a consistent API within the 6.x line. The major boundary is substantial: v6 replaces legacy repeatable-job calls with Job Schedulers and requires stored repeat metadata to be migrated in the order documented by the project. The 6.2.1 patch also repaired stalled-job key values, evidence that lock recovery behavior deserves version-matched workers during rollout.
Docs4/5The official docs give separate, task-focused pages for connections, retries, stalled jobs, rate limits, flows, concurrency, retention, graceful shutdown, Redis hosting, and migration from v5 to v6. Code examples expose operational settings such as `maxRetriesPerRequest: null` and `noeviction`. The main navigation mixes open-source BullMQ with paid Pro material, so readers must check the feature comparison before designing around groups or group-specific controls.
Maintenance5/5GitHub reported 9,333 stars, 383 open issues and pull requests, an unarchived repository, and a push on August 26, 2026. The npm registry published 6.3.0 that day. Releases 6.2.1 and 6.2.2 followed 6.2.0 with a stalled-job correction and deferred-failure rate-limit work across several language clients. That pace shows active fixes, though same-day multi-language tags make release notes worth reading rather than assuming every listed change affects Node.
Ecosystem5/5npm recorded 8,237,365 BullMQ downloads in the completed week ending August 25, 2026, and the repository README links implementations for Node, Python, Rust, Elixir, .NET, and PHP. The Node package accepts ioredis, node-redis 5, and other documented Redis-compatible clients, and Nest maintains a BullMQ integration. Paid dashboards and Pro features exist around the core, but they are separate products rather than capabilities implied by the MIT package.

Use it if

  • A web request should enqueue email, image work, imports, or webhook delivery and return before that work finishes.
  • Jobs must survive a process exit and need delayed starts, retry backoff, deduplication, priorities, or retained failure records.
  • Several Node workers need to consume the same queue while a shared datastore coordinates locks and global rate limits.
  • A workflow has child jobs that must finish before a parent job becomes eligible to run.
Skip it if

Setup reality

Our fresh Node 22 Bookworm install of bullmq 6.2.0 completed in 4.4 seconds. It left 13 packages using 13 MB, with 5 direct dependencies and 4 peer dependencies. npm audit found 0 known vulnerabilities. The package is CommonJS without an exports map, yet both require() and ESM import worked. TypeScript declarations are included, and the declared Node floor is 14.17.0. The browser bundle failed, so keep BullMQ in server and worker builds.

Redis is the normal first-run requirement. Supply connection options or an existing client, and plan for extra sockets: Worker and QueueEvents use blocking commands and may duplicate a connection. When passing an ioredis instance to a Worker, set maxRetriesPerRequest: null. BullMQ manages its own key prefix, so the docs warn against ioredis keyPrefix. The production guide also calls for Redis maxmemory-policy=noeviction; evicting queue keys can corrupt job state.

A job can execute more than once after a stalled lock or crash, so processors need idempotent side effects. Local concurrency controls parallel jobs inside one Worker, while several Worker processes add more concurrency across CPUs. The rate limiter applies across the queue, not independently to each Worker. Delayed timestamps and cron schedules mark when work becomes eligible; a busy queue can start it later than that timestamp.

Finished jobs remain in Redis unless removeOnComplete or removeOnFail sets an age or count. Cleanup is lazy and happens when another job reaches that finished state. During shutdown, worker.close() stops claims and waits for active work, but it supplies no deadline. Put the hard timeout in the process supervisor. Teams upgrading old repeatable jobs should follow the v5-to-v6 migration sequence before starting v6 workers because Job Schedulers replace the legacy repeat APIs.

Patterns

Add a job and run a worker enqueue-and-process

import { Queue, Worker } from 'bullmq';

const connection = { host: '127.0.0.1', port: 6379 };
const queue = new Queue('mail', { connection });

await queue.add('welcome', { userId: 42 });

const worker = new Worker('mail', async (job) => {
  if (job.name === 'welcome') await sendWelcome(job.data.userId);
}, { connection });

Queue and Worker must use the same queue name and datastore. Run the Worker in a long-lived server process.

Pass an ioredis client reuse-ioredis-connection

import IORedis from 'ioredis';
import { Queue, Worker } from 'bullmq';

const client = new IORedis({ maxRetriesPerRequest: null });
const queue = new Queue('exports', { connection: client });
const worker = new Worker('exports', exportFile, { connection: client });

A Worker rejects a supplied ioredis connection unless `maxRetriesPerRequest` is null. It still creates a duplicate connection for blocking commands.

Retry with exponential backoff retry-transient-failure

await queue.add('deliver', payload, {
  attempts: 5,
  backoff: { type: 'exponential', delay: 1_000 },
  removeOnComplete: 500,
  removeOnFail: 5_000,
});

The processor must throw for an attempt to fail. A retry can repeat external side effects, so use an idempotency key.

Write typed progress report-progress

import { Job, Worker } from 'bullmq';

type Input = { path: string };
type Result = { rows: number };
type Progress = { parsed: number; total: number };

const worker = new Worker<Input, Result, string, Progress>('imports',
  async (job: Job<Input, Result, string, Progress>) => {
    await job.updateProgress({ parsed: 50, total: 100 });
    return { rows: 100 };
  },
);

The ProgressType generic arrived in 6.2.0. Keep producer and worker type packages aligned during rolling deployments.

Create a recurring scheduler schedule-recurring-job

await queue.upsertJobScheduler(
  'daily-report',
  { pattern: '0 15 3 * * *', tz: 'UTC' },
  { name: 'build-report', data: { period: 'daily' } },
);

BullMQ 6 uses Job Schedulers for recurring work. Migrate legacy repeatable-job records under v5 before the v6 deployment.

Make work eligible in 30 minutes delay-job

await queue.add(
  'send-reminder',
  { invoiceId: 'inv_42' },
  { delay: 30 * 60 * 1_000 },
);

A 30-minute delay sets the earliest eligible time. Worker availability and queue load can push execution later.

Keep the latest job in a burst deduplicate-latest-job

await queue.add('sync-customer', { customerId: 42 }, {
  delay: 5_000,
  deduplication: {
    id: 'customer-42',
    ttl: 5_000,
    extend: true,
    replace: true,
  },
});

With `replace` and `extend`, a new matching job replaces the data and restarts the 5-second TTL.

Wait for child jobs compose-job-flow

import { FlowProducer } from 'bullmq';

const flows = new FlowProducer({ connection });
await flows.add({
  name: 'publish',
  queueName: 'releases',
  data: {},
  children: [
    { name: 'test', queueName: 'checks', data: { ref: 'main' } },
    { name: 'scan', queueName: 'checks', data: { ref: 'main' } },
  ],
});

The parent remains in `waiting-children` until its children complete. Every queue in the flow must share the same Redis deployment.

Limit a queue across workers cap-global-rate

const worker = new Worker('partner-api', callPartner, {
  connection,
  concurrency: 20,
  limiter: { max: 100, duration: 60_000 },
});

The 100-per-minute limit is global to the queue across Worker instances. The concurrency value is local to this Worker.

Observe results from every worker listen-queue-events

import { QueueEvents } from 'bullmq';

const events = new QueueEvents('mail', { connection });
events.on('completed', ({ jobId, returnvalue }) => {
  console.log('completed', jobId, returnvalue);
});
events.on('failed', ({ jobId, failedReason }) => {
  console.error('failed', jobId, failedReason);
});

QueueEvents covers the whole queue and holds a blocking connection. Events on one Worker cover only that Worker instance.

Retain a limited job history bound-job-history

const queue = new Queue('audit', {
  connection,
  defaultJobOptions: {
    removeOnComplete: { age: 3_600, count: 1_000 },
    removeOnFail: { age: 86_400, count: 5_000 },
  },
});

BullMQ removes old records lazily when another job completes or fails. An idle queue can retain records beyond the stated age.

Drain a worker on SIGTERM close-gracefully

process.once('SIGTERM', async () => {
  try {
    await worker.close();
    await queue.close();
  } catch (error) {
    console.error(error);
    process.exitCode = 1;
  }
});

`worker.close()` waits for active jobs without a timeout. Configure the process supervisor's 30-second or other chosen deadline separately.

Alternatives

PackageRegistryPick it when
pg-bossnpmPick it when PostgreSQL is already mandatory and adding Redis solely for background jobs is undesirable.
bee-queuenpmPick it for a narrower Redis queue when flows, Job Schedulers, and BullMQ's larger feature surface add little value.
agendanpmPick it when jobs and schedules must live in MongoDB and its document model matches the rest of the service.

More infra guides

boto3 · opentelemetry-api · @opentelemetry/api · psutil · distro · @aws-sdk/client-s3 · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.