agenda、cron、later、node-cron、node-schedule はすべて Node.js 環境で定期実行やスケジュールされたタスクを管理するためのライブラリです。これらのライブラリは、Cron 式による繰り返しジョブの設定や、特定日時での一回限りの実行といった基本機能を提供しますが、永続化、依存関係、柔軟なスケジュール記述、およびアーキテクチャ設計において大きく異なります。例えば、agenda は MongoDB を使ってジョブを永続化し、再起動後も状態を維持できる一方、cron や node-cron はメモリ上でのみ動作し、プロセス終了時にスケジュールが失われます。later は独自のスケジュール DSL を提供し、Cron 式以外の柔軟な指定が可能ですが、メンテナンスが停止されています。
Node.js で定期処理やスケジュール実行を行うには、いくつかの選択肢があります。それぞれのライブラリは「いつ」「何を」「どう実行するか」の設計思想が異なり、用途に応じて適切なものを選ぶ必要があります。この記事では、5つの主要なライブラリを実際のコードとともに比較し、現場で役立つ判断基準を示します。
最初に重要な注意点です。later は 公式に非推奨(deprecated) となっています。npm ページおよび GitHub リポジトリ(bunkat/later)は 2016 年以降更新されておらず、アーカイブ済みです。新規プロジェクトでは絶対に使用せず、代わりに cron や node-schedule を検討してください。
多くのライブラリは Unix 標準の Cron 式(例:'0 9 * * *' = 毎日9時)をサポートしています。以下に各ライブラリでの実装例を示します。
cronconst CronJob = require('cron').CronJob;
const job = new CronJob('0 9 * * *', () => {
console.log('毎日9時に実行');
}, null, true, 'Asia/Tokyo');
node-cronconst cron = require('node-cron');
cron.schedule('0 9 * * *', () => {
console.log('毎日9時に実行');
}, {
scheduled: true,
timezone: 'Asia/Tokyo'
});
node-scheduleconst schedule = require('node-schedule');
schedule.scheduleJob('0 9 * * *', () => {
console.log('毎日9時に実行');
});
agendaagenda は Cron 式を repeatEvery で使うことができますが、内部的には human-readable 文字列(例:'3 minutes')もサポートします。
const Agenda = require('agenda');
const agenda = new Agenda({ db: { address: 'mongodb://localhost/agenda' } });
agenda.define('daily task', (job) => {
console.log('毎日9時に実行');
});
(async () => {
await agenda.start();
await agenda.every('0 9 * * *', 'daily task');
})();
💡 注意:
laterは非推奨のため、コード例は省略します。
プロセスがクラッシュまたは再起動した際に、未完了のジョブを復元できるかどうかは、運用上の大きな違いです。
agenda:MongoDB で永続化agenda は MongoDB にジョブを保存するため、サーバー再起動後もスケジュールが維持されます。また、失敗したジョブのリトライやロック機構も備えています。
// MongoDB にジョブが保存される
await agenda.schedule('tomorrow at 9am', 'send email', { to: 'user@example.com' });
cron, node-cron, node-schedule):メモリ上のみこれらはすべて メモリ上でのみ スケジュールを管理します。プロセスが終了すると、登録済みのジョブはすべて失われます。
// node-cron の例:プロセス終了でスケジュール消失
const task = cron.schedule('*/5 * * * *', () => {
// 5分ごとの処理
});
// process.exit() で task は完全に消える
この特性から、cron/node-cron/node-schedule は「アプリケーション起動中にだけ有効な一時タスク」に適しています。一方、agenda は「システム全体で信頼性が求められるバッチ処理」に適しています。
Cron 式だけでは表現できないスケジュール(例:「毎月第2水曜日」)を扱いたい場合があります。
node-schedule:RecurrenceRulenode-schedule は RecurrenceRule オブジェクトで詳細なルールを定義できます。
const schedule = require('node-schedule');
const rule = new schedule.RecurrenceRule();
rule.dayOfWeek = 3; // 水曜日(0=日曜)
rule.hour = 9;
rule.minute = 0;
// ただし「第2水曜日」は直接指定不可 → 別途ロジックが必要
schedule.scheduleJob(rule, () => {
console.log('毎週水曜日9時');
});
実際には「第2水曜日」を正確に表現するには追加計算が必要ですが、
node-scheduleは Date オブジェクトやカスタムルールで柔軟に対応可能です。
later(非推奨):独自 DSLlater は 'every 2 weeks on Wednesday at 9:00' のような自然言語風の記述をサポートしていましたが、現在は使用できません。
cron、node-cron、agenda は基本的に Cron 式に依存しており、複雑なカスタムロジックは自前で実装する必要があります。
cron:コールバック中心。async/await と組み合わせにくい。node-cron:Promise 対応。async 関数を直接渡せる。node-schedule:コールバックだが、戻り値として Job オブジェクトを返し、動的制御が可能。agenda:Promise/async 対応。モダンな非同期処理に最適。// node-cron:async 対応
cron.schedule('* * * * *', async () => {
await fetchData();
});
// agenda:async 対応
agenda.define('fetch data', async (job) => {
await fetchData();
});
cron:job.start() / job.stop()node-cron:task.start() / task.stop()node-schedule:job.cancel()agenda:agenda.cancel() で条件に基づき削除可能// node-schedule:動的キャンセル
const job = schedule.scheduleJob('*/10 * * * *', () => {
console.log('10分ごと');
});
// ある条件で停止
if (needStop) job.cancel();
| ライブラリ | 外部依存 | 永続化 | 本番向き |
|---|---|---|---|
agenda | MongoDB 必須 | ✅ | ✅ |
cron | なし | ❌ | △(小規模) |
node-cron | なし | ❌ | △(小規模) |
node-schedule | なし | ❌ | △(中規模) |
later | なし(非推奨) | ❌ | ❌ |
node-cron が軽量で使いやすい。node-schedule がバランス良い。agenda 一択(MongoDB 導入が前提)。agenda(MongoDB 使用可なら)node-cron(Promise 対応で現代的)node-schedulecron(ただし新規プロジェクトでは node-cron 推奨)later は絶対に使わない最終的に、あなたのアプリケーションが「プロセス寿命を超えてジョブを維持する必要があるか?」という一点で、agenda かそれ以外かが決まります。その上で、API の使いやすさやスケジュールの柔軟性で微調整するのがベストプラクティスです。
agenda はジョブの永続化とクラスタ対応が必要な本番環境向けに最適です。MongoDB をバックエンドとして使い、再起動後も未完了のジョブを復元できるため、信頼性が求められるシステム(例:メール配信、データ同期)に適しています。ただし、MongoDB への依存があるため、インフラ要件が増える点に注意が必要です。
cron はシンプルで軽量な Cron 実装を求めている場合に適しています。標準的な Cron 式をサポートし、コールバックベースの API で簡単に使えるため、小規模なスクリプトやプロトタイプ開発に向いています。ただし、ジョブの永続化や詳細な制御機能はなく、プロセス終了時にスケジュールが消える点に留意してください。
later は非推奨(deprecated)のパッケージであり、新規プロジェクトでの使用は避けてください。最後の更新は 2016 年以降なく、公式 GitHub リポジトリもアーカイブされています。代替として cron や node-schedule を検討すべきです。
node-cron は標準的な Cron 式を使い、Promise 対応や TypeScript サポートなど現代的な API を備えています。軽量で依存が少なく、シンプルな定期タスク(例:監視スクリプト、キャッシュ更新)に最適です。ただし、ジョブの永続化や高度なスケジュールロジックはサポートしていません。
node-schedule は Cron 式だけでなく、Date オブジェクトや RecurrenceRule による柔軟なスケジュール指定が可能です。たとえば「毎月第2火曜日」のような複雑なルールも表現できます。永続化機能はありませんが、スケジュールの動的変更やキャンセルが容易で、中規模アプリケーションでの一時的なタスク管理に適しています。
A light-weight job scheduling library for Node.js
Migrating from v5? See the Migration Guide for all breaking changes.
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.
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:
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' });
| Package | Backend | Notifications | Install |
|---|---|---|---|
@agendajs/mongo-backend | MongoDB | Polling only | npm install @agendajs/mongo-backend |
@agendajs/postgres-backend | PostgreSQL | LISTEN/NOTIFY | npm install @agendajs/postgres-backend |
@agendajs/redis-backend | Redis | Pub/Sub | npm install @agendajs/redis-backend |
| Backend | Storage | Notifications | Notes |
|---|---|---|---|
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. |
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
});
import { Agenda } from 'agenda';
import { PostgresBackend } from '@agendajs/postgres-backend';
const agenda = new Agenda({
backend: new PostgresBackend({
connectionString: 'postgresql://user:pass@localhost:5432/mydb'
})
});
import { Agenda } from 'agenda';
import { RedisBackend } from '@agendajs/redis-backend';
const agenda = new Agenda({
backend: new RedisBackend({
connectionString: 'redis://localhost:6379'
})
});
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()
});
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.
// 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'
});
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.
// 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
// 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
}
});
// 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.
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) => { /* ... */ });
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.
Official Backend Packages:
Tools:
MIT