{"id":51102596,"url":"https://github.com/node-cron/nestjs","last_synced_at":"2026-06-24T12:00:44.878Z","repository":{"id":365718039,"uuid":"1273402444","full_name":"node-cron/nestjs","owner":"node-cron","description":"NestJS integration for node-cron. A drop-in replacement for @nestjs/schedule with the same decorators, plus background tasks and distributed-ready scheduling.","archived":false,"fork":false,"pushed_at":"2026-06-18T14:56:05.000Z","size":81,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T16:10:04.081Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/node-cron.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-18T13:42:37.000Z","updated_at":"2026-06-18T14:57:35.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/node-cron/nestjs","commit_stats":null,"previous_names":["node-cron/nestjs"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/node-cron/nestjs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-cron%2Fnestjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-cron%2Fnestjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-cron%2Fnestjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-cron%2Fnestjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/node-cron","download_url":"https://codeload.github.com/node-cron/nestjs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/node-cron%2Fnestjs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34731256,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-24T12:00:44.084Z","updated_at":"2026-06-24T12:00:44.871Z","avatar_url":"https://github.com/node-cron.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @node-cron/nestjs\n\nA drop-in replacement for [`@nestjs/schedule`](https://docs.nestjs.com/techniques/task-scheduling)\nbacked by [node-cron](https://github.com/merencia/node-cron).\n\nKeep the exact same decorators (`@Cron`, `@Interval`, `@Timeout`), the same\n`ScheduleModule` and `SchedulerRegistry`, and gain node-cron's extras on top:\n**distributed scheduling**, **per-fire coordination** (run once across a fleet),\n**overlap control**, **execution caps** and **jitter**.\n\n\u003e Migrating is a one-line change: swap the import from `@nestjs/schedule` to\n\u003e `@node-cron/nestjs`. Your decorated methods stay exactly as they are.\n\n## Why swap?\n\n| | `@nestjs/schedule` (uses `cron`) | `@node-cron/nestjs` (uses `node-cron`) |\n| --- | --- | --- |\n| `@Cron` / `@Interval` / `@Timeout` | yes | yes (same API) |\n| `CronExpression` enum | yes | yes (identical values) |\n| `SchedulerRegistry` | yes | yes (returns node-cron `ScheduledTask`) |\n| Skip overlapping runs | `waitForCompletion` | `waitForCompletion` / `noOverlap` |\n| Run once across a fleet (distributed) | no | `distributed: true` + a coordinator |\n| Per-fire HA coordination (Redis) | no | `@node-cron/redis-coordinator` |\n| Cap executions / random jitter | no | `maxExecutions`, `maxRandomDelay` |\n| Background tasks (forked process) | no | `@BackgroundCron` |\n\n### Differences to know before you migrate\n\nThe decorators and module API are drop-in, but two things differ on purpose:\n\n- **`SchedulerRegistry.getCronJob(name)` returns a node-cron `ScheduledTask`,\n  not the `cron` package's `CronJob`.** This is the upgrade (you get\n  `getNextRun()`, `getStatus()`, `execute()`, `on(...)`, distributed\n  coordination), but it is a breaking change in shape: code calling\n  `.nextDate()`, `.lastDate()` or reading `.running` must be updated. See\n  [SchedulerRegistry](#schedulerregistry) for the equivalents.\n- **`utcOffset` is not supported.** node-cron schedules by IANA timezone, so\n  `utcOffset` is ignored with a warning. Use `timeZone` instead.\n\n## Install\n\n```bash\nnpm install @node-cron/nestjs node-cron\n# peer deps you already have in a Nest app: @nestjs/common @nestjs/core\n```\n\n`node-cron` is a peer dependency, so install it alongside this package.\n\n## Usage\n\nIdentical to `@nestjs/schedule`.\n\n```ts\n// app.module.ts\nimport { Module } from '@nestjs/common';\nimport { ScheduleModule } from '@node-cron/nestjs';\nimport { TasksService } from './tasks.service';\n\n@Module({\n  imports: [ScheduleModule.forRoot()],\n  providers: [TasksService],\n})\nexport class AppModule {}\n```\n\n```ts\n// tasks.service.ts\nimport { Injectable, Logger } from '@nestjs/common';\nimport { Cron, Interval, Timeout, CronExpression } from '@node-cron/nestjs';\n\n@Injectable()\nexport class TasksService {\n  private readonly logger = new Logger(TasksService.name);\n\n  @Cron(CronExpression.EVERY_30_SECONDS)\n  handleCron() {\n    this.logger.log('Called every 30 seconds');\n  }\n\n  @Interval(10_000)\n  handleInterval() {\n    this.logger.log('Called every 10 seconds');\n  }\n\n  @Timeout(5_000)\n  handleTimeout() {\n    this.logger.log('Called once, 5 seconds after startup');\n  }\n}\n```\n\n### `@Cron` options\n\nEverything from `@nestjs/schedule` plus node-cron's extras:\n\n```ts\n@Cron('0 3 * * *', {\n  name: 'nightly-backup',     // retrieve it from SchedulerRegistry\n  timeZone: 'America/Sao_Paulo',\n  waitForCompletion: true,    // alias: noOverlap — skip overlapping runs\n  disabled: false,\n  initialDelay: 2_000,        // delay the first run after bootstrap\n  threshold: 250,             // missed-deadline tolerance (ms)\n\n  // node-cron extensions:\n  distributed: true,          // run once across a fleet (requires name + coordinator)\n  distributedLease: 5 * 60_000,\n  maxExecutions: 10,\n  maxRandomDelay: 1_000,      // jitter to spread fleet load\n})\nhandleCron() {}\n```\n\n\u003e `utcOffset` is not supported by node-cron (which schedules by IANA timezone).\n\u003e If set, it is ignored with a warning. Use `timeZone`.\n\n### SchedulerRegistry\n\n`getCronJob(name)` returns a node-cron\n[`ScheduledTask`](https://github.com/merencia/node-cron), so you get its full\nAPI instead of the `cron` package's `CronJob`:\n\n```ts\nimport { Injectable } from '@nestjs/common';\nimport { SchedulerRegistry } from '@node-cron/nestjs';\n\n@Injectable()\nexport class JobsController {\n  constructor(private readonly registry: SchedulerRegistry) {}\n\n  inspect() {\n    const task = this.registry.getCronJob('nightly-backup');\n    task.getNextRun();   // Date | null\n    task.getStatus();    // 'idle' | 'running' | 'stopped' | ...\n    task.execute();      // run it now, off-schedule\n    task.on('execution:failed', (ctx) =\u003e { /* ... */ });\n    task.stop();\n  }\n}\n```\n\n## Background tasks\n\nA background task runs in a **forked child process** with its own event loop,\nso heavy or blocking work never stalls your main NestJS process. This is a\nnode-cron feature that `@nestjs/schedule` does not have.\n\nDeclare one with `@BackgroundCron`. Unlike `@Cron` (which decorates a method\nwhose body runs inline), `@BackgroundCron` decorates a **property whose value is\nthe path to the task file**. The reasoning: an inline job's code lives in the\nmethod, a background job's code lives in another file, so the decorator target\nreflects where the code is.\n\n### The self-referencing single-file pattern\n\nThe cleanest layout keeps the task and its schedule in one file that points at\nitself with `__filename`:\n\n```ts\n// report.task.ts\nimport { Injectable } from '@nestjs/common';\nimport { BackgroundCron, type TaskContext } from '@node-cron/nestjs';\n\n// (A) Runs in the forked CHILD process. A plain standalone function: there is\n//     no Nest DI here. node-cron imports this compiled file and calls `task`.\nexport const task = async (ctx: TaskContext) =\u003e {\n  // heavy, isolated work\n};\n\n// (B) Runs in the MAIN process. Register ReportTask in a module's providers.\n//     The property holds this file's own path.\n@Injectable()\nexport class ReportTask {\n  @BackgroundCron('0 * * * *', { name: 'report' })\n  taskFile = __filename;\n}\n```\n\n```ts\n// some.module.ts\n@Module({ providers: [ReportTask] })\nexport class SomeModule {}\n```\n\nThat is all. On bootstrap the task is registered in `SchedulerRegistry` (under\n`name`), forked, started, and cleaned up on shutdown, exactly like a `@Cron`.\n\n### Rules and gotchas\n\n- **The task file must `export const task`.** That named export is what the\n  child process runs. Anything else in the file (the `@Injectable` class) is\n  ignored by the child.\n- **Point the property at the compiled `.js`.** Use `__filename` (CommonJS, the\n  `nest build` default) or `fileURLToPath(import.meta.url)` (ESM). It must be an\n  absolute path string; the explorer throws a clear error if the property does\n  not hold one.\n- **`export const task` gets no Nest DI.** It is a different process. It cannot\n  inject providers or read in-memory state from the main app. It can use\n  `process.env`, import plain modules, and open its own connections (see the\n  escape hatch below if you truly need DI).\n- **Fork happens once, not per fire.** node-cron forks the child when the task\n  starts and runs the schedule inside it, so the file's import cost is paid once\n  at startup, not on every run.\n- **`reflect-metadata` is handled for you.** The child re-imports the task file,\n  and decorators (including Nest's `@Injectable`) need `reflect-metadata`.\n  Importing anything from `@node-cron/nestjs` pulls it in transitively, so the\n  child is covered without you adding an import.\n- **All `@Cron` options work**, including `distributed`, `timeZone`,\n  `maxExecutions`, etc.\n\n### Distributed background tasks\n\nCombine both: a heavy job that runs in its own process **and** only on one\ninstance of the fleet per fire. The coordinator lives in the parent process and\nthe child coordinates through it over IPC, so it shares the same backend\n(e.g. Redis) as every other instance.\n\n```ts\n@BackgroundCron('0 3 * * *', {\n  name: 'nightly-backup',\n  distributed: true,\n  distributedLease: 5 * 60_000,\n})\ntaskFile = __filename;\n```\n\n### Escape hatch: DI inside a background task\n\nIf the background work genuinely needs DI, bootstrap a standalone application\ncontext inside `task`:\n\n```ts\nimport { NestFactory } from '@nestjs/core';\nimport { TaskModule } from './task.module';\nimport { ReportService } from './report.service';\n\nexport const task = async (ctx: TaskContext) =\u003e {\n  const app = await NestFactory.createApplicationContext(TaskModule, { logger: false });\n  try {\n    await app.get(ReportService).generate(ctx.date);\n  } finally {\n    await app.close();\n  }\n};\n```\n\n\u003e **Do not bootstrap your full `AppModule` here.** If you do, its\n\u003e `ScheduleModule` runs again *inside the child* and re-schedules every job in\n\u003e that process. Use a lean `TaskModule` that imports only what the task needs\n\u003e (no `ScheduleModule`). Note the context is rebuilt (and connections reopened)\n\u003e on each run, so this suits heavy, infrequent jobs.\n\n### When to use what\n\n- Heavy/isolated work, no DI needed → **`@BackgroundCron`**. Its sweet spot.\n- Needs DI, no process isolation needed → plain **`@Cron`** (main process, full\n  DI). Just don't block the event loop.\n- Needs both DI and isolation → `@BackgroundCron` + `createApplicationContext`\n  with the lean-module caveat above.\n\n## Distributed scheduling\n\nRun the same schedule on N instances and have each fire execute **once** across\nthe fleet. Provide a coordinator and mark jobs `distributed: true`.\n\n### Highly-available, per-fire coordination with Redis\n\nUse [`@node-cron/redis-coordinator`](https://github.com/node-cron/redis-coordinator):\nany instance can win each fire, and it survives the loss of any node.\n\n```ts\n// app.module.ts\nimport { Module } from '@nestjs/common';\nimport { ScheduleModule } from '@node-cron/nestjs';\nimport { RedisLockCoordinator } from '@node-cron/redis-coordinator';\nimport { createClient } from 'redis';\n\nconst redis = createClient();\nawait redis.connect();\n\n@Module({\n  imports: [\n    ScheduleModule.forRoot({\n      coordinator: new RedisLockCoordinator(redis),\n    }),\n  ],\n})\nexport class AppModule {}\n```\n\n```ts\n@Cron('0 3 * * *', {\n  name: 'nightly-backup',           // required for distributed (the coordination key)\n  distributed: true,\n  distributedLease: 5 * 60_000,     // must exceed the job's worst-case runtime\n})\nhandleBackup() {}\n```\n\nYou bring your own Redis client (`redis` or `ioredis`); the coordinator just\nuses it. See the redis-coordinator README for the full guarantee and tuning.\n\n### Per-job coordinator\n\nA coordinator passed to a single `@Cron` overrides the module-level one:\n\n```ts\n@Cron('* * * * *', { name: 'job', distributed: true, runCoordinator: myCoordinator })\nhandle() {}\n```\n\n### Async configuration\n\n```ts\nScheduleModule.forRootAsync({\n  imports: [RedisModule],\n  inject: [REDIS_CLIENT],\n  useFactory: (redis) =\u003e ({ coordinator: new RedisLockCoordinator(redis) }),\n});\n```\n\n## Module options\n\n```ts\nScheduleModule.forRoot({\n  cronJobs: true,      // register @Cron methods (default true)\n  intervals: true,     // register @Interval methods (default true)\n  timeouts: true,      // register @Timeout methods (default true)\n  useNestLogger: true, // route node-cron logs through Nest's Logger (default true)\n  coordinator,         // global node-cron RunCoordinator for distributed jobs\n});\n```\n\n## Compatibility\n\n- Node.js \u003e= 20.\n- NestJS v9, v10 and v11 (`@nestjs/common` / `@nestjs/core` are peer deps).\n- `node-cron` \u003e= 4.4.1 (peer dep).\n- Ships ESM + CJS with TypeScript types.\n\n## License\n\nISC\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnode-cron%2Fnestjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnode-cron%2Fnestjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnode-cron%2Fnestjs/lists"}