DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Background Jobs in Node.js: BullMQ, Cron, and Worker Threads

Background Jobs in Node.js: BullMQ, Cron, and Worker Threads

Some work shouldn't block an HTTP response. Background jobs handle it asynchronously.

When to Use Background Jobs

  • Sending emails (user shouldn't wait for SMTP)
  • Processing uploads (resize, convert, store)
  • Generating reports (can take minutes)
  • Syncing data with external services
  • Anything that takes > 500ms

BullMQ: Production Queue

npm install bullmq ioredis
Enter fullscreen mode Exit fullscreen mode
import { Queue, Worker } from 'bullmq';
import { Redis } from 'ioredis';

const connection = new Redis(process.env.REDIS_URL!);

// Queue definition
const emailQueue = new Queue('emails', { connection });

// Add job to queue (in your API route)
await emailQueue.add('welcome-email', {
  userId: user.id,
  email: user.email,
  name: user.name,
}, {
  attempts: 3,
  backoff: { type: 'exponential', delay: 2000 },
  removeOnComplete: true,
  removeOnFail: false, // Keep failed jobs for debugging
});

// Worker (separate process)
const worker = new Worker('emails', async (job) => {
  const { userId, email, name } = job.data;

  await resend.emails.send({
    from: 'hello@yourapp.com',
    to: email,
    subject: 'Welcome!',
    html: welcomeEmailTemplate(name),
  });

  console.log(`Welcome email sent to ${email}`);
}, { connection, concurrency: 5 });

worker.on('failed', (job, err) => {
  console.error(`Job ${job?.id} failed:`, err);
});
Enter fullscreen mode Exit fullscreen mode

Cron Jobs

// Schedule repeating jobs
await emailQueue.add(
  'weekly-digest',
  { type: 'weekly-digest' },
  {
    repeat: { cron: '0 9 * * 1' }, // Every Monday at 9 AM
    jobId: 'weekly-digest', // Prevent duplicates
  }
);
Enter fullscreen mode Exit fullscreen mode

Job Progress

// Worker: report progress
const worker = new Worker('reports', async (job) => {
  await job.updateProgress(10);
  const data = await fetchData();

  await job.updateProgress(50);
  const report = await generateReport(data);

  await job.updateProgress(90);
  await uploadToS3(report);

  await job.updateProgress(100);
  return { reportUrl: '...' };
}, { connection });

// Client: poll progress
const job = await reportQueue.getJob(jobId);
const progress = await job?.progress; // 0-100
Enter fullscreen mode Exit fullscreen mode

Bull Board: Visual Dashboard

import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
import { ExpressAdapter } from '@bull-board/express';

const serverAdapter = new ExpressAdapter();
createBullBoard({
  queues: [new BullMQAdapter(emailQueue), new BullMQAdapter(reportQueue)],
  serverAdapter,
});

app.use('/admin/queues', serverAdapter.getRouter());
// Visual dashboard at /admin/queues
Enter fullscreen mode Exit fullscreen mode

BullMQ job queues, workers, cron scheduling, and Bull Board are production-configured in the AI SaaS Starter Kit.

Top comments (0)