agenda, bee-queue, bull, bullmq, kue, and node-resque are all Node.js libraries for managing background job queues — essential for offloading time-consuming tasks like sending emails, processing files, or syncing data. They enable reliable, asynchronous task execution with features like retries, delays, and failure handling. While they share the same goal, they differ significantly in architecture (MongoDB vs Redis), API design, maintenance status, and advanced capabilities like job prioritization or TypeScript support.
When you need to run background tasks in a Node.js application — like sending emails, processing images, or syncing data — you’ll likely reach for a job queue. The six packages compared here (agenda, bee-queue, bull, bullmq, kue, and node-resque) all solve this problem but with different trade-offs in architecture, features, and maintenance status. Let’s dive into how they differ and which one fits your needs.
Before comparing features, it’s critical to know that kue is officially deprecated. Its GitHub repository states: “Kue is no longer maintained.” You should not use kue in new projects. Similarly, while agenda and node-resque are still installable, their development has slowed significantly, and they lack modern features like TypeScript support or active issue triage.
That leaves bee-queue, bull, and bullmq as the actively maintained options worth serious consideration today.
The biggest architectural split among these libraries is the underlying data store they require.
agenda and node-resque use MongoDB:
// agenda: Requires MongoDB connection
const agenda = new Agenda({ db: { address: 'mongodb://127.0.0.1/agendaDb' } });
agenda.define('send email', async (job) => {
await sendEmail(job.attrs.data.to);
});
// node-resque: Also MongoDB-based
const worker = new NR.Worker({
connection: { host: '127.0.0.1', port: 27017 },
queues: ['email'],
timeout: 5000
});
bee-queue, bull, and bullmq use Redis:
// bee-queue: Redis-backed
const Queue = require('bee-queue');
const emailQueue = new Queue('emails', { redis: { host: '127.0.0.1' } });
emailQueue.process(async (job) => {
return await sendEmail(job.data.to);
});
// bull: Redis-backed
const Queue = require('bull');
const emailQueue = new Queue('emails'); // Uses default Redis connection
emailQueue.process(async (job) => {
await sendEmail(job.data.to);
});
// bullmq: Redis-backed, TypeScript-native
import { Worker } from 'bullmq';
const worker = new Worker('emails', async (job) => {
await sendEmail(job.data.to);
});
💡 Recommendation: Unless you have strong constraints against Redis, choose a Redis-based queue. It’s the industry standard for job queues.
How you define and handle jobs varies significantly.
agenda and kue use callback-style APIs (though agenda supports async):
// kue (deprecated)
const q = require('kue').createQueue();
q.process('email', (job, done) => {
sendEmail(job.data.to, (err) => {
if (err) return done(err);
done();
});
});
bee-queue, bull, and bullmq fully embrace Promises and async/await:
// bull
queue.process('email', async (job) => {
await sendEmail(job.data.to); // throws → job fails
});
// bullmq
new Worker('email', async (job) => {
await sendEmail(job.data.to);
});
All modern queues support retries and delays, but implementation differs.
Retry strategies:
bull and bullmq let you configure backoff strategies (exponential, fixed).bee-queue supports simple retry counts.agenda supports retries but with less flexibility.// bull: Exponential backoff
queue.add('work', { foo: 'bar' }, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000
}
});
// bullmq: Same concept, cleaner syntax
await queue.add('work', { foo: 'bar' }, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000
}
});
// bee-queue: Basic retries
queue.createJob({ foo: 'bar' })
.retries(3)
.save();
Job priorities:
bull and bullmq support job priorities out of the box.// bullmq: Priority support
await queue.add('work', { foo: 'bar' }, { priority: 1 }); // 1 = highest
Visibility into your queue health matters in production.
bull and bullmq integrate with Bull Board, a dashboard showing active, completed, and failed jobs.agenda has third-party UIs like agenda-ui, but they’re less polished.bee-queue has no official UI — you’d need to build your own monitoring.kue had a built-in UI, but it’s obsolete now.// bull board setup (for bull or bullmq)
import { createBullBoard } from '@bull-board/api';
import { BullAdapter } from '@bull-board/bull';
createBullBoard({
queues: [new BullAdapter(myQueue)]
});
bullmq is written in TypeScript — full type safety out of the box.bull has community-maintained types via @types/bull.bee-queue, agenda, kue, and node-resque lack official TypeScript definitions.If your project uses TypeScript, bullmq gives you the smoothest experience.
bullmq is the official successor to bull, built on the same core ideas but rewritten with TypeScript and improved internals. Key differences:
bull uses a single Queue class for both producing and consuming.bullmq separates concerns: Queue (producer), Worker (consumer), QueueScheduler (optional for delayed jobs).// bull: Single class
const queue = new Queue('work');
queue.process(async (job) => { /*...*/ });
queue.add('work', { data: 1 });
// bullmq: Separated roles
const queue = new Queue('work');
const worker = new Worker('work', async (job) => { /*...*/ });
await queue.add('work', { data: 1 });
This separation makes testing and scaling easier but adds slight verbosity.
kueagendanode-resquebullmq if you want the most modern, well-maintained, TypeScript-friendly option with full feature parity (retries, priorities, delays, UI).bull if you’re migrating an existing codebase or prefer its simpler API and don’t need TypeScript.bee-queue only if you need a minimal, lightweight queue without priorities or complex UI — but know you’ll miss out on ecosystem tooling.bull, plan a migration to bullmq for long-term maintainability.agenda or kue, migrate to bullmq — the Redis switch is worth it for reliability and performance.| Package | Data Store | Actively Maintained? | TypeScript | Priority Jobs | Backoff Strategies | UI Available |
|---|---|---|---|---|---|---|
agenda | MongoDB | ❌ (Stale) | ❌ | ❌ | Limited | Third-party |
bee-queue | Redis | ✅ | ❌ | ❌ | Basic | ❌ |
bull | Redis | ✅ | ✅ (via DT) | ✅ | ✅ | ✅ (Bull Board) |
bullmq | Redis | ✅ | ✅ (Native) | ✅ | ✅ | ✅ (Bull Board) |
kue | Redis | ❌ (Deprecated) | ❌ | ✅ | ❌ | ✅ (Obsolete) |
node-resque | Redis | ❌ (Stale) | ❌ | ❌ | ❌ | ❌ |
Job queues are infrastructure — once you pick one, switching is painful. Go with bullmq for new work. It’s the future-proof choice that balances power, simplicity, and active maintenance. Avoid deprecated packages unless you’re maintaining legacy code with no upgrade path.
Choose bullmq for all new projects requiring a job queue. It's the actively maintained, TypeScript-native successor to bull, offering the same powerful features (priorities, advanced backoff, UI integration) with better architecture, type safety, and long-term support. It's the best balance of modern tooling and production readiness.
Choose bull if you want a mature, Redis-based queue with a rich feature set (retries with backoff, priorities, delays) and good ecosystem support like Bull Board for monitoring. It's ideal for teams not yet using TypeScript or those migrating from older queue systems who want a stable, proven solution.
Choose agenda only if you're already deeply invested in MongoDB and cannot introduce Redis, and you're maintaining a legacy system where migration isn't feasible. Avoid it for new projects due to stalled development and lack of modern features like native TypeScript support or priority queues.
Choose bee-queue if you need a lightweight, Redis-based queue with a simple API and don't require advanced features like job priorities or a monitoring UI. It's suitable for small-to-medium applications where minimal dependencies and straightforward retry logic are sufficient.
Do not choose kue for any new project — it is officially deprecated and no longer maintained. If you encounter it in legacy code, plan a migration to bullmq. Its callback-based API, lack of TypeScript support, and obsolete UI make it unsuitable for modern development.
Avoid node-resque for new work. While it offers Redis-backed queuing inspired by Ruby's Resque, it suffers from minimal maintenance, poor documentation, and no TypeScript support. Only consider it if you're extending an existing system that already uses it and migration isn't possible.
The fastest, most reliable, Redis-based distributed queue for Node.
Carefully written for rock solid stability and atomicity.
Follow @manast for *important* Bull/BullMQ/BullMQ-Pro news and updates!
You can find tutorials and news in this blog: https://blog.taskforce.sh/
Do you need to work with BullMQ on platforms other than Node.js? If so, check out the BullMQ Proxy
Supercharge your queues with a professional front end:
Sign up at Taskforce.sh
|
| Dragonfly is a new Redis™ drop-in replacement that is fully compatible with BullMQ and brings some important advantages over Redis™ such as massive better performance by utilizing all CPU cores available and faster and more memory efficient data structures. Read more here on how to use it with BullMQ. |
Some notable organizations using BullMQ:
|
|
|
|
|
|
|
|
|
Install:
$ yarn add bullmq
Add jobs to the queue:
import { Queue } from 'bullmq';
const queue = new Queue('Paint');
queue.add('cars', { color: 'blue' });
Process the jobs in your workers:
import { Worker } from 'bullmq';
const worker = new Worker('Paint', async job => {
if (job.name === 'cars') {
await paintCar(job.data.color);
}
});
Listen to jobs for completion:
import { QueueEvents } from 'bullmq';
const queueEvents = new QueueEvents('Paint');
queueEvents.on('completed', ({ jobId }) => {
console.log('done painting');
});
queueEvents.on(
'failed',
({ jobId, failedReason }: { jobId: string; failedReason: string }) => {
console.error('error painting', failedReason);
},
);
Adds jobs with parent-child relationship:
import { FlowProducer } from 'bullmq';
const flow = new FlowProducer();
const originalTree = await flow.add({
name: 'root-job',
queueName: 'topQueueName',
data: {},
children: [
{
name: 'child-job',
data: { idx: 0, foo: 'bar' },
queueName: 'childrenQueueName',
children: [
{
name: 'grandchild-job',
data: { idx: 1, foo: 'bah' },
queueName: 'grandChildrenQueueName',
},
{
name: 'grandchild-job',
data: { idx: 2, foo: 'baz' },
queueName: 'grandChildrenQueueName',
},
],
},
{
name: 'child-job',
data: { idx: 3, foo: 'foo' },
queueName: 'childrenQueueName',
},
],
});
This is just scratching the surface, check all the features and more in the official documentation
Since there are a few job queue solutions, here is a table comparing them:
| Feature | BullMQ-Pro | BullMQ | Bull | Kue | Bee | Agenda |
|---|---|---|---|---|---|---|
| Backend | redis | redis | redis | redis | redis | mongo |
| Observables | ✓ | |||||
| Group Rate Limit | ✓ | |||||
| Group Support | ✓ | |||||
| Batches Support | ✓ | |||||
| Parent/Child Dependencies | ✓ | ✓ | ||||
| Deduplication (Debouncing) | ✓ | ✓ | ✓ | |||
| Deduplication (Throttling) | ✓ | ✓ | ✓ | |||
| Priorities | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Concurrency | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Delayed jobs | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Global events | ✓ | ✓ | ✓ | ✓ | ||
| Rate Limiter | ✓ | ✓ | ✓ | |||
| Pause/Resume | ✓ | ✓ | ✓ | ✓ | ||
| Sandboxed worker | ✓ | ✓ | ✓ | |||
| Repeatable jobs | ✓ | ✓ | ✓ | ✓ | ||
| Atomic ops | ✓ | ✓ | ✓ | ✓ | ||
| Persistence | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| UI | ✓ | ✓ | ✓ | ✓ | ✓ | |
| Optimized for | Jobs / Messages | Jobs / Messages | Jobs / Messages | Jobs | Messages | Jobs |
Fork the repo, make some changes, submit a pull-request! Here is the contributing doc that has more details.
Thanks for all the contributors that made this library possible, also a special mention to Leon van Kammen that kindly donated his npm bullmq repo.