BullMQ guide for Node.js

πŸ‚ A Practical Guide to BullMQ in Node.js (With Observability Tips)

If you’ve ever needed to handle background jobs or async tasks in Node.js, you’ve likely run into tools like Bull, Agenda, or Kue. But these days, BullMQ is the go-to solution for serious queue management in modern Node environments β€” built on top of Redis with support for repeatable jobs, concurrency, retries, events, and more.

This post walks through how to set up BullMQ, build a basic job queue, and structure your system for production. And along the way, we’ll share a few lessons learned about scaling queues safely β€” especially when things go wrong.

πŸ’¨ TL;DR

  • BullMQ is a powerful Redis-based job queue library for Node.js
  • You can create queues, add jobs, and run workers easily
  • Job options let you manage retries, delays, and cleanup
  • In production, observability is key β€” monitor workers, failures, and Redis
  • Upqueue.io can help make that easy, but even logs and alerts go a long way

πŸ›  What Is BullMQ, and Why Use It?

BullMQ is a message queue library for Node.js that uses Redis to persist jobs. It’s ideal for handling:

  • Long-running tasks (e.g., sending emails, video processing)
  • Scheduled or recurring jobs
  • Offloading CPU-heavy work from your main thread

Unlike its predecessor Bull, BullMQ is built using TypeScript and offers a more modular and powerful API.

🧱 Installation & Setup

Let’s create a basic queue system using BullMQ.

1. πŸ’Ύ Installation

npm install bullmq ioredis

2. πŸ”Œ Connect to Redis

// redis-conn.ts
import { RedisOptions } from 'ioredis';

export const connection: RedisOptions = {
  host: 'localhost',
  port: 6379,
};

3. 🧾 Define a Queue

// queues.ts
import { Queue } from 'bullmq';
import { connection } from './redis-conn';

export const emailQueue = new Queue('emailQueue', { connection });

4. ✍️ Add Jobs to the Queue

// producer.ts
import { emailQueue } from './queues';

emailQueue.add('sendWelcomeEmail', {
  userId: '123',
  email: 'user@example.com',
});

5. πŸ‘·β€β™€οΈ Create a Worker

// worker.ts
import { Worker } from 'bullmq';
import { connection } from './redis-conn';

const worker = new Worker('emailQueue', async job => {
  const { userId, email } = job.data;
  await sendEmail(email);
}, { connection });

πŸ” Job Options: Retries, Delay, Cleanup

BullMQ supports tons of job-level options:

emailQueue.add('sendWelcomeEmail', {
userId: '123',
email: 'user@example.com',
}, {
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
removeOnComplete: true,
});

πŸ§ͺ Testing Locally

Start your Redis server and run:

node producer.ts
node worker.ts

You’ll now have a queue running β€” but what happens when things go wrong?

πŸ” Observability & Troubleshooting

In production, issues like these are common:

  • Jobs fail silently
  • Queues stop processing (due to missing workers)
  • Redis connections drop unexpectedly

To prevent 3am fire drills, we highly recommend implementing:

  • Failed job monitoring
  • Stalled job detection
  • Missing worker alerts
  • Queue backlog warnings

Some devs roll their own scripts or Prometheus integrations. We built Upqueue.io as a quick plug-and-play dashboard that offers these exact insights without needing to dig through logs or write Redis scripts.

πŸ’‘ Production Tips

βœ… Use removeOnComplete: true to avoid memory bloat
βœ… Monitor failed jobs with retry limits
βœ… Gracefully shut down workers using worker.close()
βœ… Namespace your queues if you use multi-tenant architecture
βœ… Set alerts for Redis max memory or dropped connections

πŸ”š Wrapping Up

BullMQ is a robust and flexible solution for queue management in Node.js apps. Whether you’re processing uploads, triggering workflows, or building AI pipelines, it’s a reliable backbone for async work.

But don’t just focus on adding jobs β€” watch how they behave, track what fails, and build in resilience.

It’ll save you hours. Maybe even a weekend.