Bree is the best job scheduler for Node.js and JavaScript with cron, dates, ms, later, and human-friendly support.
Works in Node v12.17.0+, uses worker threads (Node.js) to spawn sandboxed processes, and supports async/await, retries, throttling, concurrency, and cancelable jobs with graceful shutdown. Simple, fast, and lightweight. Made for Forward Email and Lad.
Bree was created to give you fine-grained control with simplicity, and has built-in support for workers, sandboxed processes, graceful reloading, cron jobs, dates, human-friendly time representations, and much more.
We recommend you to query a persistent database in your jobs, to prevent specific operations from running more than once.
Bree does not force you to use an additional database layer of [Redis][] or [MongoDB][] to manage job state.
In doing so, you should manage boolean job states yourself using queries. For instance, if you have to send a welcome email to users, only send a welcome email to users that do not have a Date value set yet for welcome_email_sent_at.
[npm][]:
npm install bree
[yarn][]:
yarn add bree
To see details about upgrading from the last major version, please see UPGRADING.md.
IMPORTANT: Bree v9.0.0 has several breaking changes, please see UPGRADING.md for more insight.
NOTE: Bree v6.5.0 is the last version to support Node v10 and browsers.
The example below assumes that you have a directory jobs in the root of the directory from which you run this example. For example, if the example below is at /path/to/script.js, then /path/to/jobs/ must also exist as a directory. If you wish to disable this feature, then pass root: false as an option.
Inside this jobs directory are individual scripts which are run using [Workers][] per optional timeouts, and additionally, an optional interval or cron expression. The example below contains comments, which help to clarify how this works.
The option jobs passed to a new instance of Bree (as shown below) is an Array. It contains values which can either be a String (name of a job in the jobs directory, which is run on boot) OR it can be an Object with name, path, timeout, and interval properties. If you do not supply a path, then the path is created using the root directory (defaults to jobs) in combination with the name. If you do not supply values for timeout and/nor interval, then these values are defaulted to 0 (which is the default for both, see index.js for more insight into configurable default options).
We have also documented all Instance Options and Job Options in this README below. Be sure to read those sections so you have a complete understanding of how Bree works.
// app.mjs
import Bree from 'bree';
const bree = new Bree({
// ... (see below) ...
});
// top-level await supported in Node v14.8+
await bree.start();
// ... (see below) ...
Please reference the #CommonJS example below for more insight and options.
// app.js
const path = require('path');
// optional
const ms = require('ms');
const dayjs = require('dayjs');
const Graceful = require('@ladjs/graceful');
const Cabin = require('cabin');
// required
const Bree = require('bree');
//
// NOTE: see the "Instance Options" section below in this README
// for the complete list of options and their defaults
//
const bree = new Bree({
//
// NOTE: by default the `logger` is set to `console`
// however we recommend you to use CabinJS as it
// will automatically add application and worker metadata
// to your log output, and also masks sensitive data for you
// <https://cabinjs.com>
//
// NOTE: You can also pass `false` as `logger: false` to disable logging
//
logger: new Cabin(),
//
// NOTE: instead of passing this Array as an option
// you can create a `./jobs/index.js` file, exporting
// this exact same array as `module.exports = [ ... ]`
// doing so will allow you to keep your job configuration and the jobs
// themselves all in the same folder and very organized
//
// See the "Job Options" section below in this README
// for the complete list of job options and configurations
//
jobs: [
// runs `./jobs/foo.js` on start
'foo',
// runs `./jobs/foo-bar.js` on start
{
name: 'foo-bar'
},
// runs `./jobs/some-other-path.js` on start
{
name: 'beep',
path: path.join(__dirname, 'jobs', 'some-other-path')
},
// runs `./jobs/worker-1.js` on the last day of the month
{
name: 'worker-1',
interval: 'on the last day of the month'
},
// runs `./jobs/worker-2.js` every other day
{
name: 'worker-2',
interval: 'every 2 days'
},
// runs `./jobs/worker-3.js` at 10:15am and 5:15pm every day except on Tuesday
{
name: 'worker-3',
interval: 'at 10:15 am also at 5:15pm except on Tuesday'
},
// runs `./jobs/worker-4.js` at 10:15am every weekday
{
name: 'worker-4',
cron: '15 10 ? * *',
cronValidate: {
override: {
useBlankDay: true
}
}
},
// runs `./jobs/worker-5.js` on after 10 minutes have elapsed
{
name: 'worker-5',
timeout: '10m'
},
// runs `./jobs/worker-6.js` after 1 minute and every 5 minutes thereafter
{
name: 'worker-6',
timeout: '1m',
interval: '5m'
// this is unnecessary but shows you can pass a Number (ms)
// interval: ms('5m')
},
// runs `./jobs/worker-7.js` after 3 days and 4 hours
{
name: 'worker-7',
// this example uses `human-interval` parsing
timeout: '3 days and 4 hours'
},
// runs `./jobs/worker-8.js` at midnight (once)
{
name: 'worker-8',
timeout: 'at 12:00 am'
},
// runs `./jobs/worker-9.js` every day at midnight
{
name: 'worker-9',
interval: 'at 12:00 am'
},
// runs `./jobs/worker-10.js` at midnight on the 1st of every month
{
name: 'worker-10',
cron: '0 0 1 * *'
},
// runs `./jobs/worker-11.js` at midnight on the last day of month
{
name: 'worker-11',
cron: '0 0 L * *',
cronValidate: {
useLastDayOfMonth: true
}
},
// runs `./jobs/worker-12.js` at a specific Date (e.g. in 3 days)
{
name: 'worker-12',
// <https://github.com/iamkun/dayjs>
date: dayjs().add(3, 'days').toDate()
// you can also use momentjs
// <https://momentjs.com/>
// date: moment('1/1/20', 'M/D/YY').toDate()
// you can pass Date instances (if it's in the past it will not get run)
// date: new Date()
},
// runs `./jobs/worker-13.js` on start and every 2 minutes
{
name: 'worker-13',
interval: '2m'
},
// runs `./jobs/worker-14.js` on start with custom `new Worker` options (see below)
{
name: 'worker-14',
// <https://nodejs.org/api/worker_threads.html#worker_threads_new_worker_filename_options>
worker: {
workerData: {
foo: 'bar',
beep: 'boop'
}
}
},
// runs `./jobs/worker-15.js` **NOT** on start, but every 2 minutes
{
name: 'worker-15',
timeout: false, // <-- specify `false` here to prevent default timeout (e.g. on start)
interval: '2m'
},
// runs `./jobs/worker-16.js` on January 1st, 2022
// and at midnight on the 1st of every month thereafter
{
name: 'worker-16',
date: dayjs('1-1-2022', 'M-D-YYYY').toDate(),
cron: '0 0 1 * *'
}
]
});
// handle graceful reloads, pm2 support, and events like SIGHUP, SIGINT, etc.
const graceful = new Graceful({ brees: [bree] });
graceful.listen();
// start all jobs (this is the equivalent of reloading a crontab):
(async () => {
await bree.start();
})();
/*
// start only a specific job:
(async () => {
await bree.start('foo');
})();
// stop all jobs
bree.stop();
// stop only a specific job:
bree.stop('beep');
// run all jobs (this does not abide by timeout/interval/cron and spawns workers immediately)
bree.run();
// run a specific job (...)
bree.run('beep');
(async () => {
// add a job array after initialization:
const added = await bree.add(['boop']); // will return array of added jobs
// this must then be started using one of the above methods
// add a job after initialization:
await bree.add('boop');
// this must then be started using one of the above methods
})();
// remove a job after initialization:
bree.remove('boop');
*/
For more examples - including setting up bree with TypeScript, ESModules, and implementing an Email Queue, see the examples folder.
For a more complete demo using express see: Bree Express Demo
Here is the full list of options and their defaults. See src/index.js for more insight if necessary.
| Property | Type | Default Value | Description |
|---|---|---|---|
logger |
Object | console |
This is the default logger. We recommend using [Cabin][cabin] instead of using console as your default logger. Set this value to false to disable logging entirely (uses noop function) |