NestJS
since: 1.7
@runwisp/js/nestjs has the API of @nestjs/schedule: the same module,
decorators and SchedulerRegistry. RunWisp runs the schedule, and your methods
run in the app with your injected services.
It works with NestJS 10, 11 and 12, on Node.js 20.19, 22.12 or later, or on Bun.
Set up
Section titled “Set up”npm install @runwisp/jsImport RunWispModule once, in the root module:
import { Module } from "@nestjs/common";import { RunWispModule } from "@runwisp/js/nestjs";import { DatabaseModule } from "./database.module.js";import { TasksService } from "./tasks.service.js";
@Module({ imports: [DatabaseModule, RunWispModule.forRoot()], providers: [TasksService],})export class AppModule {}forRoot() takes the new RunWisp() options.
Turn on shutdown hooks in main.ts, so running tasks finish before the app
exits:
const app = await NestFactory.create(AppModule);app.enableShutdownHooks();On shutdown, running tasks finish before any onModuleDestroy,
beforeApplicationShutdown or onApplicationShutdown hook runs, so a database
client closed in one stays open for them. This holds for app.close() too, and
whatever the order of imports.
Define tasks
Section titled “Define tasks”import { Injectable, Logger } from "@nestjs/common";import { Cron, CronExpression, Interval } from "@runwisp/js/nestjs";import { ReportsService } from "./reports.service.js";
@Injectable()export class TasksService { private readonly logger = new Logger(TasksService.name);
constructor(private readonly reports: ReportsService) {}
@Cron(CronExpression.EVERY_DAY_AT_3AM, { name: "nightly-report" }) async sendReport() { await this.reports.send(); this.logger.log("report sent"); }
@Interval("sync", 30_000) sync() {}}Each method becomes a RunWisp task. Without a name, the task is named
ClassName.methodName. What the method logs goes to the run’s log, and a
thrown error fails the run.
As with @nestjs/schedule, runs overlap: a run that comes while the last one
is still going starts anyway, unless waitForCompletion skips it. Times missed
while the app was down are not run late. The tasks get
catch_up = 0, and
max_concurrent = 1024, or
on_overlap = "skip" with
waitForCompletion. The method gets the run’s
context as its argument, with a signal
for stopping.
The app prints the dashboard address on startup.
Replace @nestjs/schedule
Section titled “Replace @nestjs/schedule”Replace @nestjs/schedule with @runwisp/js/nestjs in your imports.
ScheduleModule is another name for RunWispModule, so the rest of the code
stays as it is.
| @nestjs/schedule | @runwisp/js/nestjs |
|---|---|
@Cron(expression, opts) |
Same. timeZone, waitForCompletion and disabled work. A Date is not supported. |
@Interval(ms) |
Runs every ms milliseconds, in whole seconds: ms is a multiple of 1000. |
@Timeout(ms) |
Runs once when RunWisp starts, with run_on_start. |
SchedulerRegistry |
Jobs are task handles: stop(), start(), getNextRun(). |
addCronJob, addInterval |
Not supported. Inject RunWisp and call task(). |
@Interval throws when the app starts if ms is not a whole number of seconds,
since RunWisp would run it at a different interval. @Timeout ignores the
delay. @Cron accepts utcOffset, initialDelay, unrefTimeout and
threshold so your code compiles, ignores them, and logs a warning once for
each. Use timeZone instead of utcOffset.
@nestjs/schedule runs every job in every copy of the app. RunWisp runs each
run once per data directory, however many copies run. See
Several copies of the app.
Add tasks in code
Section titled “Add tasks in code”The module exports the RunWisp instance. Inject it for tasks with the full
set of task options:
import { Injectable, OnModuleInit } from "@nestjs/common";import { RunWisp } from "@runwisp/js/nestjs";
@Injectable()export class BackupService implements OnModuleInit { constructor(private readonly runwisp: RunWisp) {}
onModuleInit() { this.runwisp.task("backup", { cron: "0 4 * * *", retry_attempts: 3 }, () => this.backup()); }}Set RUNWISP_JS=off in your test environment, so tests that boot the app
don’t start RunWisp. See Unit tests.
Vitest, the default from Nest 12, loads @runwisp/js as is. Jest needs
version 30.4 or later, Node.js 24.9 or later, and
NODE_OPTIONS=--experimental-vm-modules. If your imports end in .js, map
them in the Jest config:
{ "jest": { "moduleNameMapper": { "^(\\.{1,2}/.*)\\.js$": "$1" } }}CommonJS projects
Section titled “CommonJS projects”@runwisp/js is an ES module. Projects that compile to CommonJS, the default
before Nest 12, load it with require(). In tsconfig.json, use
"module": "commonjs" with TypeScript 5, or "module": "nodenext" with
TypeScript 5.8 or later. "module": "node16" can’t load it.
Bun runs the app from source, decorators and injection included:
bun run src/main.tsTo keep using the Nest CLI, tell it to start the app with Bun:
bun --bun x nest start --exec bunUse RUNWISP_JS=off bun test for tests.