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
- In
apps/backend/package.json, ensure"tooling-queue-bullmq": "workspace:*"is listed underdependenciesand remove any other queue adapter package. - Run
make pnpm-lockfile-updatethenmake pnpm-install. - In
development.ts,production.ts, andtest.ts, settools.queue: - In
compose.yml, add thememoryRedis service andbullmq-adminif not already present, and pointbackend,consumer, andmigratoratdepends_on: memory: - In
.env.development, setREDIS_URL=redis://memory:6379. - Run
make test module=tooling-queue-bullmqandmake 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
- Add the queue name and its DLQ to the
QueueNameenum inapps/backend/src/utilities/queue.ts. - Add a matching config block under
tools.queue.queuesin each env config file — copy an existing entry and adjust the name and retry settings. - Add the consumer to
apps/backend/src/tools/queue/consumerRegistry.ts. Skip this if you only need a producer. - 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
memoryRedis service is shared betweentools.cacheandtools.queue. PointingREDIS_URLat a different instance for one tool will break the other unless you split the config intentionally. - Never paste a production
REDIS_URLinto.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.