Skip to main content
BullMQ is the default queue provider: Redis-backed, zero extra infrastructure in dev, and observable via Bull Board out of the box.

Why

BullMQ gives you persistent, retriable background jobs backed by Redis — the same Redis instance already used for caching. It ships with a dev UI (Bull Board), supports DLQs, and slots into the pluggable tooling system so you can swap it for another provider by changing one config value.

Setup

  1. In apps/backend/package.json, ensure "tooling-queue-bullmq": "workspace:*" is listed under dependencies and remove any other queue adapter package.
  2. Run make pnpm-lockfile-update then make pnpm-install.
  3. In development.ts, production.ts, and test.ts, set tools.queue:
    queue: {
      client: MQType.BULLMQ,
      connection: { url: process.env.REDIS_URL },
      queues: [
        {
          name: QueueName.YOUR_QUEUE,
          maxRetries: 3,
          maxMessages: 100,
          retryDelay: 1000,
          errorRetryDelaySeconds: 60,
          dlq: { name: QueueName.YOUR_QUEUE_DLQ, maxMessages: 100 },
        },
      ],
    }
    
  4. In compose.yml, add the memory Redis service and bullmq-admin if not already present, and point backend, consumer, and migrator at depends_on: memory:
    memory:
      image: redis:latest
      ports:
        - 6379:6379
      healthcheck:
        test: ["CMD", "redis-cli", "ping"]
        interval: 2s
        timeout: 5s
        retries: 10
    
    bullmq-admin:
      image: nauverse/bull-board:latest
      environment:
        - REDIS_URL=redis://memory:6379
        - BULL_VERSION=BULLMQ
      depends_on:
        - memory
      ports:
        - "3998:3000"
    
  5. In .env.development, set REDIS_URL=redis://memory:6379.
  6. Run make test module=tooling-queue-bullmq and make test module=backend.

Local observability

Bull Board runs at http://localhost:3998 — it shows queue depth, job state, retries, and DLQ contents in real time. It reads directly from Redis, so it only reflects traffic when BullMQ is the active provider.

Adding a new queue

  1. Add the queue name and its DLQ to the QueueName enum in apps/backend/src/utilities/queue.ts.
  2. Add a matching config block under tools.queue.queues in each env config file — copy an existing entry and adjust the name and retry settings.
  3. Add the consumer to apps/backend/src/tools/queue/consumerRegistry.ts. Skip this if you only need a producer.
  4. Run make test module=backend.

Gotchas

  • Bull Board only shows traffic for the active provider. If you switch providers, the UI goes dark — that’s expected.
  • Never mix queue providers between the API and consumer services in the same deployment.
  • The memory Redis service is shared between tools.cache and tools.queue. Pointing REDIS_URL at a different instance for one tool will break the other unless you split the config intentionally.
  • Never paste a production REDIS_URL into .env.development — rotate credentials immediately if that happens.

What’s next?

  • SQS — full guide to adopting SQS as an alternative provider.
  • Configuration — switching pluggable tool implementations.
  • Tooling system — how loaders and workspace packages fit together.