NestJSMiddleCoding
Что такое модуль @nestjs/schedule и как планировать задачи?
Модуль @nestjs/schedule оборачивает библиотеку node-cron и позволяет декларативно запускать задачи по расписанию через декораторы @Cron, @Interval и @Timeout прямо внутри провайдеров NestJS.
Модуль @nestjs/schedule: планирование задач
Пакет @nestjs/schedule интегрирует планировщик node-cron в экосистему NestJS. Он регистрирует задачи как провайдеры и управляет их жизненным циклом вместе с приложением.
Установка и подключение
npm install @nestjs/schedule
Подключаем ScheduleModule в корневой модуль:
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksService } from './tasks.service';
@Module({
imports: [ScheduleModule.forRoot()],
providers: [TasksService],
})
export class AppModule {}
Три вида декораторов
@Cron(expression)— запускает метод по cron-выражению.@Interval(milliseconds)— запускает метод каждые N миллисекунд (аналогsetInterval).@Timeout(milliseconds)— запускает метод один раз через N миллисекунд после старта приложения.
Пример сервиса с задачами
import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression, Interval, Timeout } from '@nestjs/schedule';
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
// Встроенная константа: каждый день в 00:00
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
handleDailyCleanup(): void {
this.logger.log('Запуск ежедневной очистки...');
}
// Произвольное cron-выражение: каждые 15 минут
@Cron('0 */15 * * * *', { name: 'fetchRates', timeZone: 'Europe/Moscow' })
async fetchExchangeRates(): Promise<void> {
this.logger.log('Обновление курсов валют');
// await this.ratesService.update();
}
// Каждые 5 секунд
@Interval(5000)
handleHeartbeat(): void {
this.logger.debug('Heartbeat tick');
}
// Один раз через 10 секунд после старта
@Timeout(10000)
handleStartupWarmup(): void {
this.logger.log('Прогрев кэша после старта');
}
}
Динамическое управление задачами
Для программного управления задачами инжектируем SchedulerRegistry:
import { Injectable } from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';
@Injectable()
export class DynamicTasksService {
constructor(private readonly schedulerRegistry: SchedulerRegistry) {}
pauseJob(name: string): void {
const job = this.schedulerRegistry.getCronJob(name);
job.stop(); // ставим на паузу
}
resumeJob(name: string): void {
const job = this.schedulerRegistry.getCronJob(name);
job.start();
}
addDynamicJob(name: string, cronTime: string, callback: () => void): void {
const job = new CronJob(cronTime, callback);
this.schedulerRegistry.addCronJob(name, job);
job.start();
}
deleteJob(name: string): void {
this.schedulerRegistry.deleteCronJob(name);
}
}
Опции декоратора @Cron
@Cron('0 0 * * * *', {
name: 'hourlySyncJob', // имя для SchedulerRegistry
timeZone: 'UTC', // часовой пояс (важно для серверов без TZ-настройки)
disabled: false, // можно отключить задачу при инициализации
})
async hourlySync(): Promise<void> { /* ... */ }
Подводные камни
- Несколько реплик — при горизонтальном масштабировании задача запустится на каждом инстансе одновременно. Необходима распределённая блокировка (Redis-lock через
redlockили аналог). - Время выполнения превышает интервал —
@Intervalне ждёт завершения предыдущего вызова. Если задача занимает больше времени, чем интервал, вызовы начнут накапливаться. - Необработанные исключения — ошибка внутри задачи не проксируется в глобальный фильтр исключений NestJS, поэтому нужно явно оборачивать тело метода в
try/catch. - Часовой пояс сервера — если
timeZoneне указан явно, задача использует часовой пояс операционной системы контейнера, что может приводить к неожиданному поведению в разных окружениях. - ScheduleModule.forRoot() вызывается один раз — повторный импорт в feature-модулях вызовет ошибку регистрации дублирующихся задач.
- Метод должен быть публичным — декораторы
@Cron,@Intervalи@Timeoutне работают наprivateиprotectedметодах из-за ограничений рефлексии TypeScript. - Тестирование — в unit-тестах
ScheduleModuleнужно мокировать через{ provide: SchedulerRegistry, useValue: mockRegistry }, иначе тесты запускают реальные таймеры.
Common mistakes
- Дает общий ответ про Node.js и не называет конкретные API NestJS.
- Не объясняет, где в lifecycle находится @nestjs/schedule.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить @nestjs/schedule на примере кода.
- Называет ключевые API: @Cron(), @Interval().
- Использует точные API NestJS, а не вымышленные hooks/decorators/methods.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.