DEV Community

trannguyenhung011086
trannguyenhung011086

Posted on

6 3

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 :)

Image of Datadog

How to Diagram Your Cloud Architecture

Cloud architecture diagrams provide critical visibility into the resources in your environment and how they’re connected. In our latest eBook, AWS Solution Architects Jason Mimick and James Wenzel walk through best practices on how to build effective and professional diagrams.

Download the Free eBook

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?

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay