DEV Community

trannguyenhung011086
trannguyenhung011086

Posted on

How I schedule jobs for my pet project with Agenda JS

While playing with my small Express app a bit (https://dev.to/trannguyenhung011086/first-try-with-express-2a3p), I started to diving more in NodeJS events method to make a simple and manageable email sending scheduler.

Below is how I develop my code from using the native NodeJS eventEmitter to Agenda JS methods.


First of all is the mailer service containing methods:

  • send active email when user register
// mailer.js

const config = require('../config');
const mailer = require('@sendgrid/mail');

mailer.setApiKey(config.sendgrid);

module.exports = {
    sendActiveEmail: async ({ username, email, url }) => {
        try {
            const msg = {
                to: email,
                from: 'learn-svelte@example.com',
                subject: 'Your active link',
                text: `Hi ${username}, please click the following url to activate your account: ${url}.\nThank you!`,
                html: `<p>Hi <strong>${username}</strong>,</p> 
                <p>Please click <a href=${url}>the following url</a> to activate your account.</p>
                <p>Thank you!</p>`,
            };
            const send = await mailer.send(msg);
            console.log('Active email is sent to ' + email);
            return send;
        } catch (err) {
            console.error('Cannot send email!\n', err);
        }
    },
};
Enter fullscreen mode Exit fullscreen mode

Then come the user service which is used for the Express user routes. Here I define 'register' event for the eventEmitter method.

// userService.js

module.exports = {
...
createUser: async ({ username, email, password }) => {
        const payload = { username, email, password };
        await validateRegister(payload);

        ...

        payload.password = await bcrypt.hash(password, 10);
        let newUser = await UserModel.create(payload);

        const send = myEmitter.emit('register', {
             username,
             email,
             url: newUser.activeLink,
         });
         if (!send) myEmitter.emit('error');

        ...

        return newUser;
    },
}
,
Enter fullscreen mode Exit fullscreen mode

Then I call mailer service in event subcribers.

// event.js

const EventEmitter = require('events');
const myEmitter = new EventEmitter();

const mailer = require('../services/mailer');

myEmitter.on('register', async ({ username, email, url }) => {
    await mailer.sendActiveEmail({ username, email, url });
});


module.exports = myEmitter;
Enter fullscreen mode Exit fullscreen mode

The above code work quite well for the case of sending active email right after user registers. But what about if I want to send a welcome email in 1 minutes after they click the active link?

First I think of using setTimeout method but it is not an easy way to scale later with more scheduled jobs. That was when I found out about Agenda JS which is a lightweight solution for such purpose.

You can check how to install Agenda as instructed at https://github.com/agenda/agenda. Please note, it requires to connect to Mongo database to work.

Then I started modifying my code a bit as below.

// agenda.js

const Agenda = require('agenda');
const config = require('../config');

const mailer = require('../services/mailer');

const agenda = new Agenda({
    db: { address: config.database, collection: 'agendaJobs' },
});

agenda
    .on('ready', () => console.log('Agenda started!'))
    .on('error', () => console.log('Agenda connection error!'));

agenda.define('register', async job => {
    const { username, email, url } = job.attrs.data;
    await mailer.sendActiveEmail({ username, email, url });
});

agenda.define('welcome', async job => {
    const { username, email } = job.attrs.data;
    await mailer.sendWelcomeEmail({ username, email });
});

agenda.start();

module.exports = agenda;
Enter fullscreen mode Exit fullscreen mode

Similar to the native event subscriber method, here I define jobs for Agenda to consume. Then in another file, I define methods for Agenda to publish.

// scheduler.js

const agenda = require('./agenda');

module.exports = {
    scheduleActiveEmail: async ({ username, email, url }) => {
        await agenda.schedule('in 1 second', 'register', {
            username,
            email,
            url,
        });
    },

    scheduleResendActiveEmail: async ({ username, email, url }) => {
        await agenda.schedule('in 1 second', 'resend', {
            username,
            email,
            url,
        });
    },

    scheduleWelcomeEmail: async ({ username, email }) => {
        await agenda.schedule('in 30 seconds', 'welcome', { username, email });
    },
};
Enter fullscreen mode Exit fullscreen mode

Next, I use the new Agenda publisher methods in user service.

// userService.js

module.exports = {
    createUser: async ({ username, email, password }) => {
        const payload = { username, email, password };
        await validateRegister(payload);

        ...

        payload.password = await bcrypt.hash(password, 10);
        let newUser = await UserModel.create(payload);

        await scheduler.scheduleActiveEmail({
            username,
            email,
            url: newUser.activeLink,
        });

        return newUser;
    },

    activeUser: async ({ userId, uuid }) => {
        const user = await getUserById(userId);

        ...

        await scheduler.scheduleWelcomeEmail({
            username: user.username,
            email: user.email,
        });

        user.active = true;
        user.welcome = true;
        await user.save();

        return { userId: user._id, active: user.active };
    },
};
Enter fullscreen mode Exit fullscreen mode

Finally, after the jobs are processed, I can query of job data stored in AgendaJobs collection on Mongo database.

agenda

In summary, Agenda JS is a suitable solution for simple management of scheduling jobs in NodeJS rather than using the native setTimeout or traditional cron jobs.

It even provides a dashboard solution which I will continue to learn more and write about it soon :)

Top comments (3)

Collapse
 
whitewarrior profile image
White Warrior

Hey, thanks, it's very helpful for me!

Collapse
 
hasnaindev profile image
Muhammad Hasnain

Do you have to pass a database URL or can you pass the database connection directly?

Collapse
 
salmanahmad94 profile image
salmanahmad94

Hey, great article! What retry logic do you propose for a job scheduling procedure developed using AgendaJS?