agenda vs cron vs later vs node-schedule vs scheduler
Task Scheduling in Node.js
agendacronlaternode-scheduleschedulerSimilar Packages:

Task Scheduling in Node.js

Task scheduling libraries in Node.js allow developers to run functions or tasks at specified intervals, times, or after a certain delay. These libraries are useful for automating repetitive tasks, sending scheduled emails, cleaning up databases, or performing any action that needs to occur at a specific time or on a regular basis. They provide APIs to define schedules using cron-like syntax, intervals, or specific dates, and handle the execution of these tasks in the background, often with support for persistence, error handling, and concurrency control. Examples include node-cron, agenda, and node-schedule, each with its own features and use cases.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
agenda164,3709,642295 kB221 days agoMIT
cron08,909161 kB223 months agoMIT
later02,419-9810 years agoMIT
node-schedule09,22535 kB1713 years agoMIT
scheduler0243,81282.7 kB1,1545 months agoMIT

Feature Comparison: agenda vs cron vs later vs node-schedule vs scheduler

Persistence

  • agenda:

    agenda stores job data in MongoDB, allowing for persistent scheduling across application restarts. This makes it suitable for long-running applications where job state needs to be maintained.

  • cron:

    cron does not provide any built-in persistence. It runs tasks in memory, which means scheduled jobs will be lost if the application crashes or restarts.

  • later:

    later is an in-memory scheduling library and does not provide persistence. Scheduled tasks are lost when the application stops. It is best for temporary or short-lived scheduling needs.

  • node-schedule:

    node-schedule does not have built-in persistence. Like cron, it runs tasks in memory, and scheduled jobs will be lost on application restart. It is suitable for non-critical tasks where persistence is not required.

  • scheduler:

    scheduler does not offer persistence features. It is designed for lightweight scheduling without the need to store job data between application restarts.

Complexity of Scheduling

  • agenda:

    agenda supports complex scheduling, including recurring jobs, delayed jobs, and job prioritization. It allows for detailed job configurations and error handling, making it suitable for enterprise-level applications.

  • cron:

    cron is best for simple to moderately complex scheduling using cron syntax. It is not designed for handling complex job workflows or dependencies.

  • later:

    later excels in complex scheduling, allowing for detailed recurrence patterns and multiple scheduling rules. It is highly flexible and can handle intricate scheduling scenarios with ease.

  • node-schedule:

    node-schedule supports both simple and complex scheduling, including cron-like expressions and JavaScript Date objects. It provides a good balance of simplicity and flexibility for most use cases.

  • scheduler:

    scheduler focuses on simplicity and supports both one-time and recurring tasks. It is not designed for highly complex scheduling but is flexible enough for most common scenarios.

Ease of Use: Code Examples

  • agenda:

    agenda provides a straightforward API for defining and managing jobs, but its MongoDB integration requires some setup. Example:

    const Agenda = require('agenda');
    const agenda = new Agenda({ db: { address: 'mongodb://localhost/agenda' } });
    
    agenda.define('send email', async job => {
      console.log('Sending email...');
    });
    
    agenda.schedule('in 1 hour', 'send email');
    agenda.start();
    
  • cron:

    cron has a simple API for scheduling tasks using cron syntax. Example:

    const { CronJob } = require('cron');
    const job = new CronJob('0 * * * *', () => {
      console.log('Running job every hour');
    });
    job.start();
    
  • later:

    later has a more complex API for defining schedules, especially for advanced users. Example:

    const later = require('later');
    later.date.localTime();
    const schedule = later.parse.text('every 1 hour');
    later.setInterval(() => {
      console.log('Running task every hour');
    }, schedule);
    
  • node-schedule:

    node-schedule offers a user-friendly API for scheduling tasks with both cron syntax and Date objects. Example:

    const schedule = require('node-schedule');
    const job = schedule.scheduleJob('*/5 * * * *', () => {
      console.log('Job running every 5 minutes');
    });
    
  • scheduler:

    scheduler provides a simple and intuitive API for scheduling tasks. Example:

    const { Scheduler } = require('scheduler');
    const scheduler = new Scheduler();
    scheduler.schedule(() => {
      console.log('Task running');
    }, 1000);
    

How to Choose: agenda vs cron vs later vs node-schedule vs scheduler

  • agenda:

    Choose agenda if you need a job scheduler that supports persistent jobs, MongoDB integration, and complex scheduling with retries and failure handling. It is ideal for applications that require reliable job processing and need to store job states in a database.

  • cron:

    Choose cron if you need a simple and lightweight solution for running tasks at specific times or intervals using cron syntax. It is best for straightforward scheduling without the need for persistence or complex features.

  • later:

    Choose later if you need a flexible scheduling library that supports complex recurrence patterns and allows for in-memory scheduling without external dependencies. It is suitable for applications that require advanced scheduling capabilities without the overhead of a full job queue.

  • node-schedule:

    Choose node-schedule if you need a feature-rich scheduler that supports both cron-like syntax and JavaScript Date objects for scheduling tasks. It is great for applications that require a mix of simple and complex scheduling without the need for persistence.

  • scheduler:

    Choose scheduler if you are looking for a modern, lightweight scheduling library that focuses on simplicity and ease of use, with support for both one-time and recurring tasks. It is ideal for projects that need a straightforward API without additional complexity.

README for agenda

Agenda

Agenda

A light-weight job scheduling library for Node.js

NPM Version NPM Downloads

Migrating from v5? See the Migration Guide for all breaking changes.

Agenda 6.x

Agenda 6.x is a complete TypeScript rewrite with a focus on modularity and flexibility:

  • Pluggable storage backends - Choose from MongoDB, PostgreSQL, Redis, or implement your own. Each backend is a separate package - install only what you need.

  • Pluggable notification channels - Move beyond polling with real-time job notifications via Redis, PostgreSQL LISTEN/NOTIFY, or other pub/sub systems. Jobs get processed immediately when saved, not on the next poll cycle.

  • Modern stack - ESM-only, Node.js 18+, full TypeScript with strict typing.

See the 6.x Roadmap for details and progress.

Features

  • Minimal overhead job scheduling
  • Pluggable storage backends (MongoDB, PostgreSQL, Redis)
  • TypeScript support with full typing
  • Scheduling via cron or human-readable syntax
  • Configurable concurrency and locking
  • Real-time job notifications (optional)
  • Sandboxed worker execution via fork mode
  • TypeScript decorators for class-based job definitions

Installation

Install the core package and your preferred backend:

# For MongoDB
npm install agenda @agendajs/mongo-backend

# For PostgreSQL
npm install agenda @agendajs/postgres-backend

# For Redis
npm install agenda @agendajs/redis-backend

Requirements:

  • Node.js 18+
  • Database of your choice (MongoDB 4+, PostgreSQL, or Redis)

Quick Start

import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';

const agenda = new Agenda({
  backend: new MongoBackend({ address: 'mongodb://localhost/agenda' })
});

// Define a job
agenda.define('send email', async (job) => {
  const { to, subject } = job.attrs.data;
  await sendEmail(to, subject);
});

// Start processing
await agenda.start();

// Schedule jobs
await agenda.every('1 hour', 'send email', { to: 'user@example.com', subject: 'Hello' });
await agenda.schedule('in 5 minutes', 'send email', { to: 'admin@example.com', subject: 'Report' });
await agenda.now('send email', { to: 'support@example.com', subject: 'Urgent' });

Official Backend Packages

PackageBackendNotificationsInstall
@agendajs/mongo-backendMongoDBPolling onlynpm install @agendajs/mongo-backend
@agendajs/postgres-backendPostgreSQLLISTEN/NOTIFYnpm install @agendajs/postgres-backend
@agendajs/redis-backendRedisPub/Subnpm install @agendajs/redis-backend

Backend Capabilities

BackendStorageNotificationsNotes
MongoDB (MongoBackend)āœ…āŒStorage only. Combine with external notification channel for real-time.
PostgreSQL (PostgresBackend)āœ…āœ…Full backend. Uses LISTEN/NOTIFY for notifications.
Redis (RedisBackend)āœ…āœ…Full backend. Uses Pub/Sub for notifications.
InMemoryNotificationChannelāŒāœ…Notifications only. For single-process/testing.

Backend Configuration

MongoDB

import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';

// Via connection string
const agenda = new Agenda({
  backend: new MongoBackend({ address: 'mongodb://localhost/agenda' })
});

// Via existing MongoDB connection
const agenda = new Agenda({
  backend: new MongoBackend({ mongo: existingDb })
});

// With options
const agenda = new Agenda({
  backend: new MongoBackend({
    mongo: db,
    collection: 'jobs'        // Collection name (default: 'agendaJobs')
  }),
  processEvery: '30 seconds', // Job polling interval
  maxConcurrency: 20,         // Max concurrent jobs
  defaultConcurrency: 5       // Default per job type
});

PostgreSQL

import { Agenda } from 'agenda';
import { PostgresBackend } from '@agendajs/postgres-backend';

const agenda = new Agenda({
  backend: new PostgresBackend({
    connectionString: 'postgresql://user:pass@localhost:5432/mydb'
  })
});

Redis

import { Agenda } from 'agenda';
import { RedisBackend } from '@agendajs/redis-backend';

const agenda = new Agenda({
  backend: new RedisBackend({
    connectionString: 'redis://localhost:6379'
  })
});

Real-Time Notifications

For faster job processing across distributed systems:

import { Agenda, InMemoryNotificationChannel } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';

const agenda = new Agenda({
  backend: new MongoBackend({ mongo: db }),
  notificationChannel: new InMemoryNotificationChannel()
});

Mixing Storage and Notification Backends

You can use MongoDB for storage while using a different system for real-time notifications:

import { Agenda } from 'agenda';
import { MongoBackend } from '@agendajs/mongo-backend';
import { RedisBackend } from '@agendajs/redis-backend';

// MongoDB for storage + Redis for real-time notifications
const redisBackend = new RedisBackend({ connectionString: 'redis://localhost:6379' });
const agenda = new Agenda({
  backend: new MongoBackend({ mongo: db }),
  notificationChannel: redisBackend.notificationChannel
});

This is useful when you want MongoDB's proven durability and flexible queries for job storage, but need faster real-time notifications across multiple processes.

API Overview

Defining Jobs

// Simple async handler
agenda.define('my-job', async (job) => {
  console.log('Processing:', job.attrs.data);
});

// With options
agenda.define('my-job', async (job) => { /* ... */ }, {
  concurrency: 10,
  lockLimit: 5,
  lockLifetime: 10 * 60 * 1000, // 10 minutes
  priority: 'high'
});

Defining Jobs with Decorators

For a class-based approach, use TypeScript decorators:

import { JobsController, Define, Every, registerJobs, Job } from 'agenda';

@JobsController({ namespace: 'email' })
class EmailJobs {
  @Define({ concurrency: 5 })
  async sendWelcome(job: Job<{ userId: string }>) {
    console.log('Sending welcome to:', job.attrs.data.userId);
  }

  @Every('1 hour')
  async cleanupBounced(job: Job) {
    console.log('Cleaning up bounced emails');
  }
}

registerJobs(agenda, [new EmailJobs()]);
await agenda.start();

// Schedule using namespaced name
await agenda.now('email.sendWelcome', { userId: '123' });

See Decorators Documentation for full details.

Scheduling Jobs

// Run immediately
await agenda.now('my-job', { userId: '123' });

// Run at specific time
await agenda.schedule('tomorrow at noon', 'my-job', data);
await agenda.schedule(new Date('2024-12-25'), 'my-job', data);

// Run repeatedly
await agenda.every('5 minutes', 'my-job');
await agenda.every('0 * * * *', 'my-job'); // Cron syntax

Job Control

// Cancel jobs (removes from database)
await agenda.cancel({ name: 'my-job' });

// Disable/enable jobs globally (by query)
await agenda.disable({ name: 'my-job' });  // Disable all jobs matching query
await agenda.enable({ name: 'my-job' });   // Enable all jobs matching query

// Disable/enable individual jobs
const job = await agenda.create('my-job', data);
job.disable();
await job.save();

// Progress tracking
agenda.define('long-job', async (job) => {
  for (let i = 0; i <= 100; i += 10) {
    await doWork();
    await job.touch(i); // Report progress 0-100
  }
});

Stopping / Draining

// Stop immediately - unlocks running jobs so other workers can pick them up
await agenda.stop();

// Drain - waits for running jobs to complete before stopping
await agenda.drain();

// Drain with timeout (30 seconds) - for cloud platforms with shutdown deadlines
const result = await agenda.drain(30000);
if (result.timedOut) {
    console.log(`${result.running} jobs still running after timeout`);
}

// Drain with AbortSignal - for external control
const controller = new AbortController();
setTimeout(() => controller.abort(), 30000);
await agenda.drain({ signal: controller.signal });

Use drain() for graceful shutdowns where you want in-progress jobs to finish their work.

Events

agenda.on('start', (job) => console.log('Job started:', job.attrs.name));
agenda.on('complete', (job) => console.log('Job completed:', job.attrs.name));
agenda.on('success', (job) => console.log('Job succeeded:', job.attrs.name));
agenda.on('fail', (err, job) => console.log('Job failed:', job.attrs.name, err));

// Job-specific events
agenda.on('start:send email', (job) => { /* ... */ });
agenda.on('fail:send email', (err, job) => { /* ... */ });

Custom Backend

For databases other than MongoDB, PostgreSQL, or Redis, implement AgendaBackend:

import { AgendaBackend, JobRepository } from 'agenda';

class SQLiteBackend implements AgendaBackend {
  readonly repository: JobRepository;
  readonly notificationChannel = undefined; // Or implement NotificationChannel

  async connect() { /* ... */ }
  async disconnect() { /* ... */ }
}

const agenda = new Agenda({
  backend: new SQLiteBackend({ path: './jobs.db' })
});

See Custom Backend Driver for details.

Documentation

Related Packages

Official Backend Packages:

Tools:

License

MIT