Getting Started with BullMQ: A Complete Guide to Distributed Job Queues in Node.js
BullMQ is the fast and robust queue system built on top of Redis for Node.js. It is a rewrite of the previous Bull, with a new API and better performance.
It is used for handling distributed jobs and messages in any application. It is backed by Redis and it is designed to have a simple and predictable API, and high reliability.
As always without further ado, let's get started with the practical example.
Prerequisites
- Bun js you can also use NodeJS
- Redis
- TypeScript (optional)
- Docker / Podman (optional) to run Redis in a container
After installing the above prerequisites, we are good to go.
All source code can be found here
Architecture of what we are going to build
Setup Redis (using Docker / Podman)
docker run -d -p 6379:6379 redis:latest
(Without Docker / Podman)
redis-server
Setup Node.js Project
npm init -y
# or
yarn init -y
# or
bun init
It will create a package.json file. Now, let's install the required dependencies.
Installing required dependencies and devDependencies
# dependencies
bun install bullmq dotenv express
bun add -D @types/express # typescript is not required with bun
To setup the typescript project follow this here
Create queue.ts file
import { Queue } from "bullmq";
export const myQueue = new Queue("firstQueue", {
connection: {
url: "redis://localhost:6379", // replace with your redis url
},
});
Create the basic express app with two routes
code can be found here
- Route to view the number of messages in the queue
// imports and constants
app.get("/", async (req, res) => {
const messages = await myQueue.getWaiting();
const completedJobs = await myQueue.getCompletedCount();
res.json({
messages: messages.map((item) => item.data),
completedJobs,
});
});
- Route to add a message to the queue
app.post("/", async (req, res) => {
const { message } = req.body;
if (!message) {
res.status(400).json({ message: "Message is required" });
return;
}
await myQueue.add(
"myJob",
{ message },
{
// configs... for more info visit <https://docs.bullmq.io/guide/queues>
removeOnComplete: true,
}
);
res.json({ message: "Message added to the queue" });
});
Now every time we hit the post request, the message will be added to the queue and we can view the number of messages in the queue by hitting the get request.
*Note We haven’t configured any consumers yet*
get request -
post request -
Creating consumer (workers)
in bullMQ the consumers is also refers as workers. To create the worker which is independent to our producer (express app). As I am using bun so I can directly compile the typescript file, you can create the javascript file in the same directory as our express app. So we don’t need to reinstall the dependencies.
Our structure will look similar like
📂application
🗃️ node_modules
📁 index.ts
📁 package.json
📁 worker.js|ts
📁 queue.ts
File content
import { Worker } from "bullmq";
new Worker(
"firstQueue", // same name as specified in producer queue
async function (job) {
console.log(`job ${job.id} started -> "${job.data.message}"`);
await new Promise((res) => setTimeout(res, 3000));
console.log(`job ${job.id} completed ✅ -> "${job.data.message}"`);
},
{
connection: {
url: "redis://localhost:6379",
},
}
);
Run the file, it will tries to establish the connection with redis and start implementing the jobs which are available in queue.
E.g. you haven’t run the worker and push some messages in queue using express app. And when you run worker file then it will pick the jobs from the queue and starts executing.
Let’s add the following messages in queue using following script - [”hey”,”watch”,”one piece”, “anime”, “it’s great!”]
(async function () {
const messages = ["hey", "watch", "one piece", "anime", "it’s great!"];
for (const message of messages) {
const req = await fetch("http://localhost:3000", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
message,
}),
});
const res = await req.json();
if (res.message) {
console.log(`Message ${message} added ✅`);
}
}
})();
After running the script we'll get the following output:
Check the keys in redis db using keys * command
Now run worker file, It will immediately pick the jobs from the queue and start executing it one by one.
Now let the worker run and try adding new messages while worker is running. You’ll notice that consumer start’s consuming message immediately.
That’s all technical knowledge you know for getting started with bullmq .
Let’s discuss some of it’s practical applications:
1. Background Task Processing
- Example: Sending emails, processing images, or running data analysis.
- Usage: Offload time-consuming tasks to a queue, ensuring the main application remains responsive.
2. Workflow Orchestration
- Example: Managing multi-step processes like onboarding workflows, order processing, or CI/CD pipelines.
- Usage: Define complex workflows with multiple dependent jobs.
3. Video or Image Processing
- Example: Encoding videos, resizing images, or generating thumbnails.
- Usage: Queue and process media files in worker nodes.
4. Real-time Notifications
- Example: Sending SMS, push notifications, or alerts.
- Usage: Schedule and manage notifications for delivery at specific times or conditions.
In conclusion, BullMQ is a powerful and versatile tool for handling job queues and background tasks in Node.js applications. Its integration with Redis allows for high performance, scalability, and reliability, making it suitable for diverse use cases such as background processing, workflow orchestration, rate limiting, and more.
