js

How to Use Agenda with NestJS for Scalable Background Job Scheduling

Learn how to integrate Agenda with NestJS to handle background tasks like email scheduling and data cleanup efficiently.

How to Use Agenda with NestJS for Scalable Background Job Scheduling

I was building a notification system for a client last week when I hit a familiar wall. The core API was fast, but sending thousands of welcome emails brought everything to a grinding halt. The user clicked ‘sign up,’ and then they waited. That’s a poor experience. It got me thinking about the silent workhorses of modern applications—the background jobs that handle everything from sending emails to cleaning up data. If you’re building with NestJS and need that kind of reliable, scheduled task processing, I want to show you a powerful combination. Let’s look at how to bring Agenda, a solid job scheduler, into your NestJS project. This setup can turn a slow, blocking operation into a smooth, background process that users never have to wait for.

Why consider Agenda? It’s a job scheduler that uses MongoDB to store its state. This is its biggest strength. Jobs, their schedules, and their results live in your database. If your server restarts, the jobs persist. If you need to scale out by running multiple instances of your app, they can all work from the same queue. You don’t need to introduce another piece of infrastructure like Redis. For many projects, especially those already using MongoDB, this is a much simpler path.

So, how do we make NestJS and Agenda work together? The goal is to create a clean module that we can import anywhere in our application. We’ll wrap Agenda’s functionality in a service that fits neatly into NestJS’s dependency injection system. This means any other service or controller can easily queue a job without worrying about the low-level details.

First, we need to install the core packages. You’ll need agenda and @types/agenda for TypeScript support.

npm install agenda
npm install --save-dev @types/agenda

Now, let’s build the core service. We’ll create an AgendaService that sets up the connection and provides methods to schedule jobs. Notice how we use the OnModuleInit lifecycle hook. This ensures our Agenda instance starts only after the module is ready.

// agenda.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { Agenda } from 'agenda';

@Injectable()
export class AgendaService implements OnModuleInit, OnModuleDestroy {
  private agenda: Agenda;

  constructor() {
    // Connect to your MongoDB instance
    this.agenda = new Agenda({
      db: { address: process.env.MONGODB_URI },
    });
  }

  async onModuleInit() {
    await this.agenda.start();
    console.log('Agenda job scheduler started');
  }

  async onModuleDestroy() {
    await this.agenda.stop();
    console.log('Agenda job scheduler stopped');
  }

  // A method to schedule a one-time job in the future
  scheduleJob(jobName: string, when: Date, data: any): void {
    this.agenda.schedule(when, jobName, data);
  }

  // A method to run a job every 5 minutes
  defineRecurringJob(jobName: string, interval: string): void {
    this.agenda.define(jobName, async (job) => {
      console.log(`Executing job: ${jobName}`, job.attrs.data);
      // Your job logic goes here
    });
    this.agenda.every(interval, jobName);
  }

  // Expose the Agenda instance for more complex use cases
  getAgendaInstance(): Agenda {
    return this.agenda;
  }
}

But what does a job actually look like in practice? Let’s define a real one. Imagine you need to send a follow-up email 24 hours after a user signs up. Instead of making the user wait, your API can simply drop a job into Agenda. Here’s how you might define that email job processor.

// welcome-email.job.ts
import { Injectable } from '@nestjs/common';
import { AgendaService } from './agenda.service';

@Injectable()
export class WelcomeEmailJob {
  constructor(private agendaService: AgendaService) {
    this.define();
  }

  define() {
    const agenda = this.agendaService.getAgendaInstance();

    agenda.define('send_welcome_followup', async (job) => {
      const { userId, email } = job.attrs.data;
      console.log(`Sending follow-up email to ${email} for user ${userId}`);
      // In reality, you would call your email service here
      // await this.emailService.sendFollowUp(userId);
    });
  }

  // This method is called from your user service after signup
  scheduleFollowUp(userId: string, userEmail: string) {
    const in24Hours = new Date(Date.now() + 24 * 60 * 60 * 1000);
    this.agendaService.scheduleJob('send_welcome_followup', in24Hours, {
      userId,
      email: userEmail,
    });
  }
}

Now, you can inject WelcomeEmailJob into your UserService. When a user signs up, you call scheduleFollowUp. It returns instantly, and the email sends a day later, all without the user noticing. Have you considered what happens if the email service is temporarily down when the job runs? Agenda has a built-in answer for that.

Agenda handles failure gracefully. You can configure jobs to retry automatically. This is crucial for operations that depend on external services. You can define the number of retries and the delay between them directly in the job definition. This turns a potential point of failure into a self-healing process.

agenda.define('send_report', { concurrency: 1, priority: 'high' }, async (job) => {
  // This job will run with high priority, and only one instance at a time
  await generateAndSendReport();
});

Monitoring is straightforward. Since all jobs are documents in MongoDB, you can query their state. You can see which jobs are scheduled, running, or failed. For a simple dashboard, you could even build a small admin panel that lists pending jobs. This transparency is a huge advantage when debugging.

Is this setup right for every scenario? No. If you need to process hundreds of thousands of jobs per minute, a Redis-based system like Bull might be faster. But for most applications—sending emails, generating nightly reports, updating caches—Agenda with NestJS is more than capable. It reduces complexity by leveraging your existing database.

The real benefit is structure. By wrapping Agenda in a NestJS service, you keep your code organized. Job logic is contained in dedicated classes. They can use dependency injection to access your database models, configuration, or other services. This is the NestJS way: building complex features from simple, testable blocks.

I encourage you to start with a single job. Take that slow, blocking function in your code and move it into a scheduled task. You’ll be surprised how much more responsive your application feels. Your users will thank you for the snappy interface, and you’ll thank yourself for the cleaner architecture.

If you found this walk-through helpful, please share it with another developer who might be wrestling with background jobs. Have you tried a different job queue with NestJS? What was your experience? Let me know in the comments below—I read every one.


As a best-selling author, I invite you to explore my books on Amazon. Don’t forget to follow me on Medium and show your support. Thank you! Your support means the world!


101 Books

101 Books is an AI-driven publishing company co-founded by author Aarav Joshi. By leveraging advanced AI technology, we keep our publishing costs incredibly low—some books are priced as low as $4—making quality knowledge accessible to everyone.

Check out our book Golang Clean Code available on Amazon.

Stay tuned for updates and exciting news. When shopping for books, search for Aarav Joshi to find more of our titles. Use the provided link to enjoy special discounts!


📘 Checkout my latest ebook for free on my channel!
Be sure to like, share, comment, and subscribe to the channel!


Our Creations

Be sure to check out our creations:

Investor Central | Investor Central Spanish | Investor Central German | Smart Living | Epochs & Echoes | Puzzling Mysteries | Hindutva | Elite Dev | JS Schools


We are on Medium

Tech Koala Insights | Epochs & Echoes World | Investor Central Medium | Puzzling Mysteries Medium | Science & Epochs Medium | Modern Hindutva

Keywords: nestjs,agenda job scheduler,background jobs,nodejs,mongodb



Similar Posts
Blog Image
Build a Distributed Task Queue System with BullMQ, Redis, and TypeScript Tutorial

Learn to build scalable distributed task queues with BullMQ, Redis & TypeScript. Master job processing, error handling, scaling & monitoring for production apps.

Blog Image
Build Type-Safe Event-Driven Architecture with TypeScript Node.js and Redis Streams

Learn to build type-safe event-driven architecture with TypeScript, Node.js & Redis Streams. Includes event sourcing, error handling & monitoring best practices.

Blog Image
How to Integrate Next.js with Prisma ORM: Complete Type-Safe Database Setup Guide

Learn how to integrate Next.js with Prisma ORM for type-safe, scalable web apps. Master database management, API routes, and SSR with our complete guide.

Blog Image
Build Type-Safe Event-Driven Microservices with NestJS, RabbitMQ, and Prisma Complete Guide

Learn to build scalable event-driven microservices with NestJS, RabbitMQ & Prisma. Complete guide with type safety, error handling & deployment best practices.

Blog Image
Production-Ready Event-Driven Microservices: NestJS, RabbitMQ, Redis Tutorial for Scalable Architecture

Learn to build scalable event-driven microservices with NestJS, RabbitMQ & Redis. Master inter-service communication, error handling & production deployment.

Blog Image
Build Type-Safe Event-Driven Architecture: TypeScript, NestJS & RabbitMQ Complete Guide 2024

Learn to build scalable, type-safe event-driven systems using TypeScript, NestJS & RabbitMQ. Master microservices, error handling & monitoring patterns.