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
Why Bun and Elysia Are Changing How We Build Fast, Type-Safe APIs

Discover how Bun and Elysia deliver blazing-fast performance and end-to-end type safety for modern API development.

Blog Image
Why Adonis.js and Lucid ORM Are a Game-Changer for TypeScript Backends

Discover how Adonis.js and Lucid ORM streamline TypeScript backend development with seamless integration and type-safe workflows.

Blog Image
Build High-Performance File Upload System: Multer, Sharp, AWS S3 in Node.js

Build a high-performance Node.js file upload system with Multer, Sharp & AWS S3. Learn secure uploads, image processing, and scalable storage solutions.

Blog Image
Complete Guide to EventStore CQRS Implementation with Node.js and Event Sourcing

Learn to build scalable event-driven apps with EventStore and Node.js. Master CQRS, event sourcing, projections, and performance optimization. Complete guide with code examples.

Blog Image
Why Lit and Shoelace Are the Future of Framework-Agnostic Web Components

Discover how combining Lit and Shoelace enables fast, reusable, and framework-independent UI development using native web components.

Blog Image
Complete Guide to Building Multi-Tenant SaaS Applications with NestJS, Prisma, and Row-Level Security 2024

Learn to build secure multi-tenant SaaS apps with NestJS, Prisma & PostgreSQL RLS. Complete guide covers authentication, database design & deployment.