Ganesh Mani I'm a full-stack developer, Android application/game developer, and tech enthusiast who loves to work with current technologies in web, mobile, the IoT, machine learning, and data science.

Getting started with Bree.js

6 min read 1857

The Bree.js logo over a white background.

This article covers what Bree.js is and how you can get started by building a practical application.

What is Bree.js?

Bree.js is a job scheduler for Node.js applications. Job scheduling is the process of executing a piece of code in a scheduled, repetitive manner.

There are other job schedulers available in the Node.js community. However, there are some good reasons why we should probably go with Bree.js over the alternatives.

Why BreeJS?

Worker Threads

Bree.js uses worker threads under the hood. Since worker threads are available in the Node.js LTS version, it’s better to go with threads for background jobs rather than using main threads.

Concurrency, throttling, and retries

Bree.js helps you run the jobs concurrently and with a retries option. Sometimes, you need to retry a specific operation inside the job. You can achieve that using libraries like this, among others.

Install and Setup

Let’s install Bree.js and see how to get started with a simple scheduled job in Node.js:

npm install bree

After that, let’s create a folder called jobs in the root directory, which contains all the background jobs:

The file for Bree.js jobs.

BreeJS

A Node.js repl by ganeshmani

bree.start() will start the scheduler and run all the jobs that are configured. Here, we have a jobs array, which contains all the background jobs. The name should match the file name declared in the jobs directory.

We made a custom demo for .
No really. Click here to check it out.

For example, here we have a job test. So, the jobs directory should contain a file called test.js to run this job. We can write our logic inside test.js, which runs with the background job.

Once we run our script, we can run our job and execute the logic inside our job script file.

Worker job test.

Schedule a job

So far, we have seen how to run a job on script start. Now, let’s see how to schedule a job using Bree.js. Scheduling a job is simple. You just need to pass an additional parameter to a jobs array element:

The index section of Bree.js.

Here, we have a parameter interval, which runs the script every 5 seconds.

const Bree = require('bree')
const bree = new Bree({
  jobs : [
    // runs the job on Start
    'test',
    {
      name : 'sample',
      interval : '5s'
    }
  ]
})
bree.start()

Our output will look like this:

The worker for our test.

There are other options you can use to schedule the job, including the following:

timeout

Timeout runs the script after a specified time in the jobs configuration. You can use this as an alternative to setTimeout in JavaScript.

const Bree = require('bree')
const bree = new Bree({
  jobs : [
    // runs the job on Start
    'test',
    {
      name : 'sample',
      timeout : '30s' //run the script after 30 seconds from the start
    }
  ]
})
bree.start()

cron

Cron is simply for running a cron job in Node.js. We need to specify the cron along with the job. Use cases for cron jobs include backing up your database and running scripts according to a specified hour, day, month, or year.

const Bree = require('bree')
const bree = new Bree({
  jobs : [
    // runs the job on Start
    'test',
    {
      name : 'sample',
      cron : '* * * * *'
      // timeout : '30s' //run the script after 30 seconds from the start
    }
  ]
})
bree.start()

So far, we have seen how to schedule a job. Now, we will see how to pass a value to our jobs from the main file.

Passing data

Since Bree.js uses a worker thread, you can use the same concept for passing data to a worker thread. All you need to do is pass the data in workerData, and it will be available in the respective job file:

const Bree = require('bree')
const bree = new Bree({
  jobs : [
    // runs the job on Start
    // 'test',
    {
      name : 'sample',
      worker: {
        workerData: {
          foo: 'bar',
          beep: 'boop'
        }
      }
      // cron : '* * * * *'
      // timeout : '30s' //run the script after 30 seconds from the start
    }
  ]
})
bree.start()
const { Worker, isMainThread, workerData } = require('worker_threads');
console.log("worker data",workerData.foo)

We have now begun to scratch the surface of Bree.js.

The above details are already available in the documentation for Breejs. I don’t want to run through the documentation again in this article, since it is already well documented.

Instead, we are going to build a small application that uses Bree.js along. In this way, we will learn how to use Bree.js in a practical application.

Here, we are going to build a Twitter scheduler application. It allows the user to schedule a Tweet at a specified time. Let’s see how to build this application using Bree.js:

Install and Setup

Let’s install the required dependancies for this application:

npm install express bree body-parser cabin ejs twit passport passport-twitter
  • express – web server to handle the requests in the server side
  • bree – job scheduler to schedule Tweets
  • body-parser – lib to parse the POST request body
  • cabin – logging library (it’s recommended by Bree.js to use for logging)
  • ejs – template engine to render the web pages
  • twit – Twitter client library to post the data to Twitter
  • passport – Used to handle Twitter authentication

Next, create app.js in the root directory and add the following code:

const express = require("express");
const bodyParser = require("body-parser");
const expressLayouts = require("express-ejs-layouts");
const passport = require("passport");
const session = require("express-session");
const mongoose = require("mongoose");
const app = express();
const routes = require("./routes");
const dotenv = require("dotenv");
const Bree = require("bree");
const Cabin = require("cabin");
dotenv.config({});

const MONGO_USER = process.env.MONGODB_USER;
const MONGO_PASSWORD = process.env.MONGODB_PASSWORD;

// MONGODB Connection
mongoose
  .connect(
    <url>,
    { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }
  )
  .then((res) => {
    console.log("mongodb connected successfully");
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));

    // Template Engine
    app.use(expressLayouts);
    app.set("view engine", "ejs");

    //Passport Configuration
    require("./config/passport")(passport);

    // Express Session for the application
    app.use(
      session({
        secret: "abcdefg",
        resave: true,
        saveUninitialized: false,
      })
    );

    // passport initialization
    app.use(passport.initialize());
    app.use(passport.session());

    // breejs configuration.
    const bree = new Bree({
      //   logger: new Cabin(),
      jobs: [{ name: "tweet-schedule", interval: "1m" }],
    });
    bree.start();

    //routes
    app.use("/", routes);

    //PORT for our application.
    const PORT = process.env.PORT || 4500;
    app.listen(PORT, () => {
      console.log(`Server is running on PORT ${PORT}`);
    });
  })
  .catch((err) => {
    console.log("Error in mongodb connection", err);
  });

We have a MongoDB connection functionality, which connects through the MongoDB URI:

mongoose
  .connect(
    <url>,
    { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }
  ){
     //logic comes here
   }

After that, we have to implement the body parser for the POST request and our setup for the template engine, which is ejs:

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(expressLayouts);
app.set("view engine", "ejs");

After that, we can set up the express session and passport initialization:

app.use(
      session({
        secret: "abcdefg",
        resave: true,
        saveUninitialized: false,
      })
);
app.use(passport.initialize());
app.use(passport.session());

Finally, we’ll set up Bree.js with a jobs configuration to fulfill Tweet-scheduling functionality.

const bree = new Bree({
      //   logger: new Cabin(),
      jobs: [{ name: "tweet-schedule", interval: "1m" }],
    });
    bree.start();

Scheduler logic

Let’s write the logic for our scheduler job functionality.

Create a file, tweet-scheduler.js, inside the jobs directory. Now, add the following code:

const { parentPort } = require("worker_threads");
const Cabin = require("cabin");
const { Signale } = require("signale");
const Jobs = require("../models/Jobs");
const User = require("../models/User");
const mongoose = require("mongoose");
const moment = require("moment-timezone");
const tweetUtils = require("../lib/tweetUtils");
const cabin = new Cabin({
  axe: {
    logger: new Signale(),
  },
});
let isCancelled = false;
if (parentPort) {
  parentPort.once("message", (message) => {
    if (message === "cancel") isCancelled = true;
  });
}
(async () => {
  await mongoose.connect(
    <URL>,
    { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }
  );
  const tweetJobs = await Jobs.find({ isActive: true }).exec();
  await Promise.all(
    tweetJobs.map(async (tweet) => {
      return new Promise(async (resolve, reject) => {
        try {
          if (isCancelled) return;
          const user = await User.findById({ _id: tweet.userId }).exec();
          if (
            moment().tz(tweet.timezone).format("YYYY-MM-DD HH:mm") <
            moment(tweet.scheduledTime, "YYYY-MM-DD HH:mm").format(
              "YYYY-MM-DD HH:mm"
            )
          ) {
            console.log("It it not time yet to post tweet");
            // return;
            resolve();
          } else {
            console.log("==================");
            try {
              await tweetUtils({
                accessToken: user.twitter.accessToken,
                accessSecret: user.twitter.refreshToken,
                tweet: tweet.body,
              });
              await Jobs.findOneAndUpdate(
                { _id: tweet._id },
                { isActive: false }
              );
            } catch (e) {
              cabin.error(e);
            }
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    })
  );
  if (parentPort) parentPort.postMessage("done");
  else process.exit(0);
})();
// cabin.info("tweet schedule jobb");
// console.log("==============================================");

I know it might be overwhelming at first glance. Let’s break it down step by step to gain a better understanding of what’s happening.

First, we have the Cabin.js logger set up. Then, we check to see if the parent port of our worker thread sends any messages. If the parentPort of the worker thread sends a message of the type cancel, then we set isCancelled to true.

const cabin = new Cabin({
  axe: {
    logger: new Signale(),
  },
});
let isCancelled = false;
if (parentPort) {
  parentPort.once("message", (message) => {
    if (message === "cancel") isCancelled = true;
  });
}

Here’s the logic for our application:

A flowchart showing how to connect to MongoDB.

We need to check if we can do few things in the background jobs. They include:

  • Getting data from the database so we can schedule Tweets from the database
  • Using Async/Await inside the Bree.js jobs file
  • Updating when the job is done

To access data from the database, we need to connect with the database separately in our background jobs even though we have connection logic in app.js:

mongoose.connect(
   <URL>,
    { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }
  );

After that, we need the Immediately Invoking Function Execution(IIFE) inside our job with async functionality to use Async/Await.

(async () => {
  await mongoose.connect(
    <URL>,
    { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true }
  );
  // Logic comes Here
})();

Then, we need to find all the active Tweets from the database using this query:

const tweetJobs = await Jobs.find({ isActive: true }).exec();

Once we get all the active Tweets, we loop through it and send the Tweets. With our scheduler, we want to send these Tweets at a time that is less than or equal to our current time:

await Promise.all(
    tweetJobs.map(async (tweet) => {
      return new Promise(async (resolve, reject) => {
        try {
          if (isCancelled) return;
          const user = await User.findById({ _id: tweet.userId }).exec();
          if (
            moment().tz(tweet.timezone).format("YYYY-MM-DD HH:mm") <
            moment(tweet.scheduledTime, "YYYY-MM-DD HH:mm").format(
              "YYYY-MM-DD HH:mm"
            )
          ) {
            console.log("It it not time yet to post tweet");
            // return;
            resolve();
          } else {
            console.log("==================");
            try {
              await tweetUtils({
                accessToken: user.twitter.accessToken,
                accessSecret: user.twitter.refreshToken,
                tweet: tweet.body,
              });
              await Jobs.findOneAndUpdate(
                { _id: tweet._id },
                { isActive: false }
              );
            } catch (e) {
              cabin.error(e);
            }
            resolve();
          }
        } catch (e) {
          reject(e);
        }
      });
    })
  );

Finally, when we are done with jobs, we can post a message to the parent thread saying the job is done.

if (parentPort) parentPort.postMessage("done");
  else process.exit(0);

Conclusion

Ultimately, you should use whichever framework or library you find easiest to implement so you can confidently say the library has done a good job. By this simple criteria, I feel  Bree.js is the perfect library to use. You can use Bree.js to implement as complex a logic as you want.

For an in-depth look at Bree.js, check out the documentation of here and here.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    : Debug JavaScript errors easier by understanding the context

    Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.

    LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to find out exactly what the user did that led to an error.

    LogRocket records console logs, page load times, stacktraces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!

    .
    Ganesh Mani I'm a full-stack developer, Android application/game developer, and tech enthusiast who loves to work with current technologies in web, mobile, the IoT, machine learning, and data science.

    Leave a Reply