Job Queues

Defer Slow Work — Keep Requests Fast

Job Queues

Move slow tasks (emails, image processing, webhook fan-out) to a background worker. BullMQ is the standard.

4 min read Level 2/5 #nodejs#queues#bullmq
What you'll learn
  • Recognize the queue pattern
  • Use BullMQ with Redis
  • Handle retries and failures

Some work is too slow for a request: sending email, resizing images, calling a flaky third-party API. Push it onto a queue — a separate process picks it up.

The Pattern

HTTP request → enqueue job → reply 202 Accepted

               background worker → do the slow thing

BullMQ — The Standard

Uses Redis under the hood. Battle-tested.

npm install bullmq

Producer (Web Server)

import { Queue } from "bullmq";

const emailQueue = new Queue("email", {
  connection: { host: "localhost", port: 6379 },
});

app.post("/signup", async (req, res) => {
  // 1. Save user — fast
  const user = await db.users.create(req.body);

  // 2. Enqueue the email — also fast
  await emailQueue.add("welcome", { userId: user.id });

  res.status(201).json(user);
});

Worker (Separate Process)

// worker.mjs — run with: node worker.mjs
import { Worker } from "bullmq";

new Worker("email", async (job) => {
  if (job.name === "welcome") {
    await sendWelcomeEmail(job.data.userId);
  }
}, { connection: { host: "localhost", port: 6379 } });

Workers can run on a different machine. They poll Redis for jobs and execute them.

Retries

await emailQueue.add("welcome", { userId: 42 }, {
  attempts: 5,
  backoff: { type: "exponential", delay: 1000 },   // 1s, 2s, 4s, 8s, 16s
});

Flaky external APIs? Set retries. BullMQ handles exponential backoff for you.

Scheduling

// run in 1 hour
await emailQueue.add("reminder", { userId: 42 }, { delay: 60 * 60 * 1000 });

// repeating
await emailQueue.add("daily-digest", {}, {
  repeat: { pattern: "0 9 * * *" },   // 9am daily (cron syntax)
});

Why You Want a Queue

  • Decoupling — slow consumers don’t slow down the request
  • Reliability — failed jobs retry; if a worker crashes, the job goes back in the queue
  • Throttling — limit concurrency so you don’t hammer a third party
  • Scheduling — run at a specific time

Without Redis

For simple cases, a plain in-memory queue + setImmediate works. But you lose: persistence across crashes, scaling beyond one process, visibility. For anything user-facing: use BullMQ + Redis.

Pub/Sub →