BeeQueue Deep Dive: The Lightweight Redis Queue You've Been Overlooking
If you've ever built a Node.js application that needs background job processing, you've probably come across BullMQ — the dominant Redis-backed queue library with 500K+ weekly downloads, a rich feature set, and an ecosystem of monitoring tools.
But there's another queue library that deserves your attention: BeeQueue.
BeeQueue is the lightweight, high-performance Redis queue you've been overlooking. At roughly ~1,000 lines of code with minimal dependencies, it does one thing and does it exceptionally well: enqueue jobs, process them, and return results — all coordinated through Redis with atomic operations and at-least-once delivery guarantees.
In this deep dive, we'll explore what makes BeeQueue unique, how it compares to BullMQ, when you should (and shouldn't) choose it, and how to monitor BeeQueue queues effectively. Whether you're building a prototype, a high-throughput worker pool, or just want to understand your queue options better, this guide has you covered.
What Is BeeQueue?
BeeQueue (npm package: bee-queue) is a simple, fast, and robust job/task queue for Node.js, backed by Redis. It was created by Lewis Ellis — the original author of the venerable kue library — and later adopted and maintained by Mixmax at production scale.
The library was announced on the Redis blog in August 2017 with the v1.0 release. Mixmax had been running Bull in production but hit a double-processing race condition at scale. While Bull v3 fixed the race condition, it introduced a performance regression. Rather than switching to RabbitMQ or another heavyweight solution, Mixmax built on top of BeeQueue — and it has been running tens of millions of jobs per day through BeeQueue ever since.
Design Philosophy
BeeQueue's design can be summarized in three words: simple, fast, robust.
- Simple: The entire library is ~1,000 lines of code with minimal dependencies. The API is a single
Queueclass — no separateWorker,FlowProducer, orQueueSchedulerto learn. - Fast: BeeQueue maximizes throughput by minimizing Redis calls. It uses Lua scripting for atomic operations, Redis pipelining for batch operations, and
BRPOPLPUSHfor non-polling job retrieval. The theoretical minimum is just 3 Redis calls per job. - Robust: Atomic operations ensure data integrity. Stalled-job detection provides at-least-once delivery guarantees. The test suite has high code coverage.
npm Status
- Latest version: 2.0.0
- Weekly downloads: ~100K (compared to BullMQ's ~500K)
- TypeScript: Type definitions are included in the package (
index.d.ts) - Redis requirement: Redis 2.8+ (Redis 3.2+ recommended for delayed jobs)
- Maintenance status: Stable and production-proven, but maintenance-mode. The core is rock-solid and sees fewer updates — which is a feature, not a bug, for a library this focused.
BeeQueue vs BullMQ: The Key Differences
The most common question developers ask is: "Should I use BeeQueue or BullMQ?" The answer depends entirely on your use case. Let's break down the differences.
Feature Comparison Table
| Feature | BeeQueue | BullMQ |
|---|---|---|
| Backend | Redis (node-redis) |
Redis (ioredis) |
| Codebase size | ~1,000 LOC | ~30,000+ LOC |
| TypeScript | Typings included | Native TypeScript |
| Priorities | ❌ Not built-in | ✅ Numeric priority |
| Delayed jobs | ✅ (delayUntil) |
✅ (delay) |
| Repeatable / cron | ❌ | ✅ (cron patterns) |
| Job flows / dependencies | ❌ | ✅ (FlowProducer) |
| Rate limiting | ❌ | ✅ (per-worker limiter) |
| Concurrency | ✅ (per-process) | ✅ (per-worker + sandboxed) |
| Retry + backoff | ✅ (immediate/fixed/exponential) | ✅ (built-in + custom) |
| Progress tracking | ✅ (Pub/Sub) | ✅ |
| Events | ✅ (local + Pub/Sub) | ✅ (global event system) |
| Stalled job detection | ✅ (checkStalledJobs) |
✅ (automatic) |
| Dashboard | Arena (community) | Bull Board (community) |
| Sandboxed processors | ❌ | ✅ (child process isolation) |
| Weekly downloads | ~100K | ~500K |
Simplicity vs Features
BeeQueue: You create a queue, create jobs, and process jobs. That's it. The entire API is a single Queue class. There's no conceptual separation between producers and workers — any queue instance can do both. This makes BeeQueue incredibly easy to learn and use.
BullMQ: BullMQ separates concerns into distinct classes — Queue, Worker, FlowProducer, QueueScheduler, and JobScheduler. While this provides flexibility for complex workflows, it also introduces significant cognitive overhead. If you're building a system that just needs reliable job processing, BullMQ can feel like overkill.
Performance Profile
BeeQueue benchmarks favorably against Bull and older BullMQ versions on raw throughput for simple jobs. The benchmark/ folder in the BeeQueue repository shows this clearly. However, BeeQueue's speed advantage narrows when you don't need advanced features — if you need priorities, flows, or rate limiting, you'll either add complexity yourself or switch to BullMQ anyway.
Dependency Footprint
BeeQueue depends on redis (the node-redis client). Total install size: tiny. BullMQ depends on ioredis, lodash-types, semver, and more, resulting in a significantly larger node_modules footprint. If you're deploying to AWS Lambda, serverless environments, or Docker images where every megabyte counts, this difference matters.
When the Differences Matter
BeeQueue wins when you need simple worker pools, high-throughput pipelines, minimal DevOps overhead, or projects that just need a reliable queue without feature bloat.
BullMQ wins for complex workflows with job chaining and flows, cron-based scheduling, rate-limited APIs, or teams that need sandboxed job isolation.
Setting Up BeeQueue: Basic Usage
Let's get our hands dirty with some code.
Installation
npm install bee-queue
That's it. You'll also need a running Redis instance (redis-server on localhost, or a remote connection).
Creating a Queue
const Queue = require('bee-queue');
// Simplest form — connects to localhost:6379
const mathQueue = new Queue('math');
// With custom Redis host
const emailQueue = new Queue('email', {
redis: { host: 'redis.example.com', port: 6379, password: 'secret' },
isWorker: false, // producer-only, won't attempt to process jobs
});
Adding Jobs
const job = mathQueue.createJob({ x: 2, y: 3 });
await job.save();
console.log(`Created job ${job.id}`);
BeeQueue's createJob supports a chainable API for configuring job behavior:
mathQueue
.createJob({ x: 10, y: 20 })
.setId('custom-id-123')
.timeout(5000) // fail if not processed in 5s
.retries(3) // retry up to 3 times
.backoff('exponential', 1000) // backoff strategy + delay factor
.delayUntil(Date.now() + 60000) // delayed 60 seconds
.save();
For bulk operations, BeeQueue supports pipelining:
const jobs = [
mathQueue.createJob({ x: 1, y: 2 }),
mathQueue.createJob({ x: 3, y: 4 }),
mathQueue.createJob({ x: 5, y: 6 }),
];
const errors = await mathQueue.saveAll(jobs);
// errors is a Map<Job, Error> — only failed saves appear
Processing Jobs
// Callback style
mathQueue.process((job, done) => {
const result = job.data.x + job.data.y;
done(null, result);
});
// Async/await style (recommended)
mathQueue.process(async (job) => {
return job.data.x + job.data.y;
});
// With concurrency (process up to 5 jobs simultaneously per process)
mathQueue.process(5, async (job) => {
return job.data.x + job.data.y;
});
A few important notes about .process():
- It can only be called once per Queue instance.
- Multiple processes or servers can each call
.process()— Redis coordinates work distribution. - The handler must return a Promise or call
done(err)/done(null, result).
Getting Results Back to Producers
One of BeeQueue's standout features is its built-in Pub/Sub event system for real-time job result tracking:
const job = mathQueue.createJob({ x: 2, y: 3 }).save();
job.on('succeeded', (result) => {
console.log(`Job ${job.id} succeeded with result: ${result}`);
});
job.on('failed', (err) => {
console.error(`Job ${job.id} failed:`, err.message);
});
job.on('progress', (progress) => {
console.log(`Job ${job.id} progress:`, progress);
});
Advanced Features
Job Retries and Backoff Strategies
BeeQueue supports multiple retry strategies out of the box, plus custom strategies:
queue.createJob({ email: 'user@example.com' })
.retries(5)
.backoff('fixed', 2000) // wait 2s between each retry
.save();
// Built-in strategies:
// 'immediate' - retry immediately (delayFactor ignored)
// 'fixed' - wait `delayFactor` ms between retries
// 'exponential' - wait `delayFactor * 2^attempt` ms
// Custom strategy:
queue.backoffStrategies['custom'] = (attempt, delayFactor) => {
return Math.min(delayFactor * Math.pow(3, attempt), 30000);
};
You can also listen for retry events:
queue.on('retrying', (job, err) => {
console.log(`Job ${job.id} failed with "${err.message}", retrying...`);
});
queue.on('failed', (job, err) => {
console.error(`Job ${job.id} exhausted all retries:`, err);
});
Delayed / Scheduled Jobs
Delayed jobs are supported through the delayUntil method, but you must opt in by setting activateDelayedJobs: true:
const delayedQueue = new Queue('notifications', {
activateDelayedJobs: true,
});
// Delay by 1 hour
delayedQueue
.createJob({ userId: 42, message: 'Your report is ready' })
.delayUntil(Date.now() + 3600000)
.save();
// Delay until a specific date
const newYear = new Date('2027-01-01T00:00:00Z');
delayedQueue
.createJob({ resolution: 'learn BeeQueue' })
.delayUntil(newYear)
.save();
Important: Without activateDelayedJobs: true (and at least one worker running), delayed jobs will simply remain in the delayed state and never be processed.
Concurrency Control
// Process 10 jobs concurrently (per Node.js process)
queue.process(10, async (job) => {
// This runs with up to 10 in-flight jobs
await doWork(job.data);
});
Default concurrency is 1. Multiple processes on different machines can each call .process() with their own concurrency settings — Redis handles distribution transparently.
Progress Reporting
// In the worker handler
queue.process(async (job) => {
const total = job.data.items.length;
for (let i = 0; i < total; i++) {
await processItem(job.data.items[i]);
job.reportProgress({ current: i + 1, total });
}
return 'done';
});
// On the producer side
job.on('progress', (progress) => {
console.log(`${progress.current} / ${progress.total} items processed`);
});
Stalled Job Detection
Stalled jobs — jobs that were claimed by a worker but never completed — are automatically detected and re-enqueued to ensure at-least-once delivery:
// Automatic: Each worker "checks in" to Redis every stallInterval ms (default 5000).
// If a worker goes silent, its jobs are re-enqueued.
// Manual stall check (useful after queue restart):
const numStalled = await queue.checkStalledJobs();
console.log(`Re-enqueued ${numStalled} stalled jobs`);
Redis Connections
BeeQueue delegates connection management to the redis package and supports a variety of connection configurations:
// String URL
const q1 = new Queue('myqueue', {
redis: 'redis://:password@host:6379/0',
});
// Object configuration
const q2 = new Queue('myqueue', {
redis: {
host: 'my-redis-cluster.example.com',
port: 6379,
password: 'supersecret',
db: 0,
},
});
// Reuse an existing Redis client
const client = require('redis').createClient(process.env.REDIS_URL);
const q3 = new Queue('myqueue', {
redis: client,
});
TLS / SSL Connections (AWS ElastiCache, Upstash)
const q = new Queue('myqueue', {
redis: {
host: 'your-cluster.xxxxxx.ng.0001.use1.cache.amazonaws.com',
port: 6379,
tls: {
servername: 'your-cluster.xxxxxx.ng.0001.use1.cache.amazonaws.com',
},
},
});
Using Valkey (Redis Alternative)
Valkey is wire-compatible with Redis, so the same client options work:
const valkeyQueue = new Queue('myqueue', {
redis: {
host: 'localhost',
port: 6379, // Valkey default port (same as Redis)
},
});
Error Handling and Reconnection
const queue = new Queue('critical-jobs', {
redis: {
host: 'redis.example.com',
retry_strategy: (options) => {
if (options.error && options.error.code === 'ECONNREFUSED') {
return 1000; // Reconnect after 1 second
}
if (options.total_retry_time > 1000 * 60 * 60) {
return new Error('Redis connection lost'); // Give up after 1 hour
}
return Math.min(options.attempt * 100, 30000); // Exponential backoff
},
},
});
queue.on('error', (err) => {
console.error('Queue error:', err.message);
});
queue.on('ready', () => {
console.log('Queue connected and ready');
});
Monitoring BeeQueue Queues
Once you have BeeQueue running in production, you need visibility into queue health. Here are the best approaches.
Built-in Health Checks
const health = await queue.checkHealth();
console.log(health);
// {
// waiting: 42,
// active: 3,
// succeeded: 1500,
// failed: 12,
// delayed: 5,
// newestJob: '2026-06-30T12:00:00.000Z'
// }
Querying Jobs
// Get a single job by ID
const job = await queue.getJob('some-job-id');
// List jobs by state with pagination
const waitingJobs = await queue.getJobs('waiting', { size: 20, start: 0 });
const failedJobs = await queue.getJobs('failed', { size: 10, start: 0 });
Arena Dashboard
Arena is the de-facto community dashboard for BeeQueue (and Bull/BullMQ):
npm install bull-arena
const Arena = require('bull-arena');
const Bee = require('bee-queue');
Arena({
Bee,
queues: [
{
name: 'addition',
hostId: 'Math Worker',
type: 'bee',
redis: { host: 'localhost', port: 6379 },
},
{
name: 'email',
hostId: 'Email Worker',
type: 'bee',
redis: { host: 'localhost', port: 6379 },
},
],
}, {
basePath: '/arena',
port: 4567,
});
Arena provides job counts by state, pagination and filtering, job detail inspection with stack trace permalinks, one-click retry for failed jobs, and support for multi-Redis backends.
QueueHub: SaaS Monitoring for BeeQueue
While Arena is a great self-hosted option, running and maintaining a monitoring dashboard adds operational overhead — especially when you have queues across multiple projects, environments, or teams.
QueueHub is a SaaS dashboard that supports both BullMQ and BeeQueue queues natively. Because BeeQueue and BullMQ share similar Redis key structures, QueueHub can inspect BeeQueue queues without any additional configuration.
QueueHub gives you:
- Multi-backend support — one dashboard for all your queues across projects and environments
- Queue health overview — at-a-glance job counts, throughput, and error rates
- Job management — retry, promote, remove, and inspect individual jobs with their full data
- Live worker view — see which workers are active and what they're processing
- Agent tunneling — for private Redis instances behind firewalls
- Multi-org with team invitations
- Support for TLS, Valkey, and AWS ElastiCache
With QueueHub, you don't need to set up Arena, manage authentication, or worry about uptime. Just connect your Redis instance (or deploy our lightweight agent) and start monitoring in minutes.
When to Choose BeeQueue Over BullMQ
Let's cut through the noise. Here's when BeeQueue is the clear winner — and when you should stick with BullMQ.
BeeQueue Is the Better Choice When…
-
You need simplicity. Your queue pattern is "create job → process job → get result." No chained workflows, no cron scheduling, no rate limiting. BeeQueue's single
Queueclass is all you need — and you can go from zero to production in minutes. -
You value high throughput for simple jobs. BeeQueue's minimal Redis overhead means it can outperform BullMQ on raw throughput for thousands of simple jobs per second. If your jobs are small and fast (milliseconds to seconds), the lightweight architecture shines.
-
You want minimal dependencies. ~1,000 LOC vs 30,000+. A smaller attack surface, faster installs, simpler debugging, and less to audit. For security-conscious teams or deployment-constrained environments, this matters.
-
You need real-time Pub/Sub events. BeeQueue's built-in progress reporting and result events — implemented via Redis Pub/Sub — are a first-class feature, not an afterthought. Producers can subscribe to job lifecycle events and get instant feedback.
-
You're building a prototype or MVP. BeeQueue gets you a working, production-grade queue in minutes. You can always migrate to BullMQ later if you outgrow it. Most projects never outgrow it.
-
You already have Redis monitoring tooling. BeeQueue uses standard Redis data structures (lists, hashes, sorted sets). Your existing Redis monitoring — RedisInsight, Prometheus Redis exporter, etc. — works without modification.
Stick With BullMQ When…
-
You need job priorities. BullMQ's numeric priority system is mature and well-tested. BeeQueue has no built-in priority support.
-
You need repeatable/cron jobs. BullMQ's
repeatoption with cron patterns is seamless. BeeQueue has no repeatable job support — you'd need to implement scheduling yourself. -
You need job flows (parent/child dependencies). BullMQ's
FlowProducerhandles dependency graphs. BeeQueue has no orchestration primitives. -
You need rate limiting. BullMQ's per-worker
limiterprevents API throttling. BeeQueue doesn't have this. -
You need sandboxed processors. BullMQ can run job handlers in child processes for crash isolation. BeeQueue runs handlers in-process only.
-
You need an actively maintained library with rapid releases. BullMQ sees frequent releases and active development. BeeQueue's core is stable but maintenance-mode.
Quick Decision Guide
Do you need cron/repeatable jobs? → BullMQ
Do you need job priorities? → BullMQ
Do you need job flows/dependencies? → BullMQ
Do you need rate limiting? → BullMQ
Do you need sandboxed processors? → BullMQ
Do you want minimal dependencies? → BeeQueue
Are your jobs short and high-volume? → BeeQueue
Do you just need "enqueue and process"? → BeeQueue
Do you want Pub/Sub progress events? → BeeQueue
Final Thoughts
BeeQueue is the unsung hero of Node.js job queues. It sacrifices features for simplicity and speed, making it the perfect choice for teams that need a reliable, lightweight, Redis-backed job queue without the cognitive overhead of more complex alternatives.
At ~1,000 lines of code with a single Queue class, it's trivial to understand, debug, and extend. When your use case is "enqueue a job, process it, get the result back" — and you don't need cron scheduling, priorities, or workflow orchestration — BeeQueue is often the better choice, even if it's not the most popular one.
And when you do need to monitor your BeeQueue queues in production, QueueHub has you covered with real-time dashboards, job management, and multi-environment support — all without the operational overhead of self-hosted solutions.
Try QueueHub for free and get instant visibility into your BeeQueue and BullMQ workloads.
Related Articles
Testing and Development Workflows for BullMQ vs BeeQueue
A practical guide to testing job queue code with BullMQ and BeeQueue — test fixtures, producer and worker testing patterns, local development workflows, and CI integration.
BeeQueue Production Patterns and Redis Internals: Beyond the Basics
Go beyond basic BeeQueue setup. Master Redis key internals, Prometheus health checks, dead letter queues, graceful shutdown, batch processing, and benchmarking methodology.
Beyond Static Priority — Building a Dynamic Priority Escalation Engine with BullMQ
Learn how to build a dynamic priority escalation engine for BullMQ with three production-ready patterns: configurable escalation rules, multi-factor priority scoring, and priority-weighted worker pool routing with complete TypeScript code.