Пошук уроків, статей та іншого контенту
Створите періодичні й одноразові завдання за розкладом за допомогою NestJS та Cron.
Заплановане завдання — це код, який запускається автоматично:
через певний інтервал часу;
відповідно до Cron-розкладу;
один раз у визначений момент.
У NestJS для цього використовується пакет @nestjs/schedule. Він інтегрує планувальник із життєвим циклом NestJS і надає декоратор @Cron.
Типові приклади:
очищення тимчасових даних кожної ночі;
синхронізація з зовнішнім сервісом кожні 10 хвилин;
надсилання нагадування в конкретний час;
періодична перевірка стану обробки завдань.
Заплановані завдання виконуються всередині процесу Node.js. Якщо застосунок запущено у кількох екземплярах, кожен екземпляр виконуватиме завдання окремо.
Встановіть пакет:
npm install @nestjs/scheduleПідключіть планувальник у кореневому модулі застосунку:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksService } from './tasks.service';
@Module({
imports: [
ScheduleModule.forRoot(),
],
providers: [TasksService],
})
export class AppModule {}ScheduleModule.forRoot() реєструє планувальник і дозволяє NestJS знаходити методи з декораторами @Cron.
Cron-вираз описує, коли саме потрібно запускати завдання.
У NestJS вираз може містити шість полів:
секунда хвилина година день-місяця місяць день-тижняНаприклад:
*/10 * * * * *Цей вираз означає: запускати завдання кожні 10 секунд.
Інші приклади:
0 * * * * *Запуск на початку кожної хвилини.
0 0 * * * *Запуск щогодини.
0 0 2 * * *Запуск щодня о 02:00.
0 30 9 * * 1-5Запуск о 09:30 з понеділка по п’ятницю.
У деяких інструментах Cron використовується формат із п’ятьма полями, без секунд. У NestJS потрібно враховувати шість полів, якщо секундне поле не пропущене явно.
@CronСтворимо сервіс із завданням, яке запускається кожні 10 секунд:
// src/tasks.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron('*/10 * * * * *')
handlePeriodicTask(): void {
this.logger.log('Періодичне завдання виконано');
}
}Після запуску застосунку NestJS автоматично викликатиме handlePeriodicTask кожні 10 секунд.
Для поширених інтервалів можна використовувати готові константи CronExpression:
import { Injectable, Logger } from '@nestjs/common';
import {
Cron,
CronExpression,
} from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron(CronExpression.EVERY_MINUTE)
handleEveryMinute(): void {
this.logger.log('Завдання виконується щохвилини');
}
}Константа CronExpression.EVERY_MINUTE зрозуміліша за ручний Cron-вираз і зменшує ризик помилки.
Завданню можна надати ім’я через опції декоратора:
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron('0 */5 * * * *', {
name: 'cleanup-task',
})
cleanup(): void {
this.logger.log('Очищення тимчасових даних');
}
}Ім’я корисне, якщо завдання потрібно отримати, зупинити або видалити програмно через SchedulerRegistry.
За потреби часовий пояс можна вказати в опціях:
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron('0 0 9 * * *', {
name: 'morning-report',
timeZone: 'Europe/Kyiv',
})
createMorningReport(): void {
this.logger.log('Формування ранкового звіту');
}
}Тепер завдання запускатиметься о 09:00 за часовим поясом Europe/Kyiv.
Часовий пояс потрібно вказувати явно, якщо розклад має відповідати локальному часу, а сервер може працювати в іншому часовому поясі.
Для одноразового запуску в майбутньому можна створити CronJob із конкретною датою. Для керування таким завданням використовується SchedulerRegistry.
// src/tasks.service.ts
import {
Injectable,
Logger,
OnModuleInit,
} from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
@Injectable()
export class TasksService implements OnModuleInit {
private readonly logger = new Logger(TasksService.name);
constructor(
private readonly schedulerRegistry: SchedulerRegistry,
) {}
onModuleInit(): void {
const runAt = new Date(Date.now() + 15_000);
const job = new CronJob(runAt, () => {
this.logger.log('Одноразове завдання виконано');
// Після виконання завдання видаляємо його з реєстру.
this.schedulerRegistry.deleteCronJob('one-time-task');
});
this.schedulerRegistry.addCronJob('one-time-task', job);
job.start();
this.logger.log(
`Одноразове завдання заплановано на ${runAt.toISOString()}`,
);
}
}У цьому прикладі:
після запуску модуля обчислюється час через 15 секунд;
створюється CronJob із конкретною датою;
завдання додається до SchedulerRegistry;
викликається job.start();
після виконання завдання видаляється з реєстру.
Для створення CronJob використовується пакет cron, який встановлюється як залежність разом із @nestjs/schedule. Якщо у версії проєкту він не встановився автоматично, виконайте:
npm install cronНижче наведено мінімальний приклад застосунку з періодичним і одноразовим завданнями.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksService } from './tasks.service';
@Module({
imports: [
ScheduleModule.forRoot(),
],
providers: [TasksService],
})
export class AppModule {}// src/tasks.service.ts
import {
Injectable,
Logger,
OnModuleInit,
} from '@nestjs/common';
import {
Cron,
CronExpression,
SchedulerRegistry,
} from '@nestjs/schedule';
import { CronJob } from 'cron';
@Injectable()
export class TasksService implements OnModuleInit {
private readonly logger = new Logger(TasksService.name);
constructor(
private readonly schedulerRegistry: SchedulerRegistry,
) {}
@Cron(CronExpression.EVERY_10_SECONDS, {
name: 'health-check-task',
})
runHealthCheck(): void {
this.logger.log('Перевірка стану системи');
}
onModuleInit(): void {
const runAt = new Date(Date.now() + 15_000);
const job = new CronJob(runAt, () => {
this.logger.log('Одноразове завдання виконано');
// Видаляємо завдання після виконання.
this.schedulerRegistry.deleteCronJob('welcome-task');
});
this.schedulerRegistry.addCronJob('welcome-task', job);
job.start();
this.logger.log(
`Одноразове завдання заплановано на ${runAt.toISOString()}`,
);
}
}Запустіть застосунок:
npm run start:devУ логах ви побачите повідомлення про перевірку кожні 10 секунд і одноразове повідомлення приблизно через 15 секунд після запуску.
SchedulerRegistry дає змогу отримувати та керувати зареєстрованими Cron-завданнями.
Наприклад, можна зупинити завдання за його ім’ям:
import {
Injectable,
Logger,
} from '@nestjs/common';
import {
Cron,
SchedulerRegistry,
} from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
constructor(
private readonly schedulerRegistry: SchedulerRegistry,
) {}
@Cron('*/10 * * * * *', {
name: 'report-task',
})
generateReport(): void {
this.logger.log('Звіт сформовано');
}
stopReportTask(): void {
const job = this.schedulerRegistry.getCronJob('report-task');
job.stop();
this.logger.log('Завдання формування звіту зупинено');
}
}Отримати завдання можна лише за ім’ям, яке було вказано в опціях @Cron.
Програмне керування зазвичай потрібне, коли розклад залежить від налаштувань користувача або змінюється під час роботи застосунку.
Метод Cron-завдання може бути асинхронним:
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron(CronExpression.EVERY_HOUR)
async synchronizeData(): Promise<void> {
this.logger.log('Початок синхронізації');
await this.loadData();
this.logger.log('Синхронізацію завершено');
}
private async loadData(): Promise<void> {
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
}Якщо завдання виконує запити до бази даних або зовнішнього API, помилки потрібно обробляти так само, як і в іншому асинхронному коді:
@Cron('0 */5 * * * *')
async synchronizeData(): Promise<void> {
try {
await this.loadData();
} catch (error) {
this.logger.error('Помилка синхронізації', error);
}
}Не варто залишати помилки без обробки, особливо якщо завдання запускається регулярно.
ScheduleModule.forRoot()Якщо модуль планувальника не підключено, декоратори @Cron не працюватимуть.
@Module({
imports: [
ScheduleModule.forRoot(),
],
})
export class AppModule {}Вираз:
*/10 * * * *може бути сприйнятий як формат із п’ятьма полями, а вираз:
*/10 * * * * *явно містить секунди й означає запуск кожні 10 секунд.
Якщо застосунок запущено у двох процесах, один Cron-метод виконається двічі — по одному разу в кожному процесі.
Це важливо для:
надсилання електронних листів;
списання коштів;
створення звітів;
очищення або зміни даних.
Для таких операцій потрібно враховувати кількість екземплярів застосунку та не допускати небажаного повторного виконання.
Дата для new Date(...) обчислюється в момент створення провайдера. Якщо дата має залежати від даних запиту або налаштувань користувача, створюйте CronJob у відповідному методі сервісу після отримання цих даних.
Без імені завдання його складніше отримати через SchedulerRegistry. Якщо потрібне програмне керування, задайте ім’я:
@Cron('0 * * * * *', {
name: 'hourly-task',
})Для роботи із запланованими завданнями встановіть @nestjs/schedule.
У кореневому модулі підключіть ScheduleModule.forRoot().
Декоратор @Cron використовується для регулярних запусків.
Cron-вираз у NestJS може містити секунди та складається із шести полів.
Часовий пояс задається через опцію timeZone.
Для одноразового запуску в конкретну дату використовуйте CronJob і SchedulerRegistry.
Іменуйте завдання, якщо плануєте керувати ними програмно.
Пам’ятайте, що в кількох екземплярах застосунку кожен екземпляр виконує власні Cron-завдання.