Add a Backend Job
Scaffold a job stub and record it in the module manifest.
Prerequisites
- A Webstir workspace initialized via
webstir init .... - Backend source at
src/backend/.
Steps
- Create a job named
cleanup:webstir add-job cleanup --workspace "$PWD"- Creates
src/backend/jobs/cleanup/index.tswith arun()function. - Adds
{ name: "cleanup" }towebstir.moduleManifest.jobsinpackage.json.
- Add metadata as needed:
- Schedule:
webstir add-job nightly --workspace "$PWD" --schedule "0 0 * * *" - Description:
--description "Nightly maintenance window" - Priority:
--priority 5(accepts numbers or free-form labels)
- Schedule:
- Iterate on the job implementation, then rebuild or watch the workspace.
- Run jobs locally:
- List metadata:
bun build/backend/jobs/scheduler.js --list - Export metadata for an external scheduler:
bun build/backend/jobs/scheduler.js --json - Run once:
bun build/backend/jobs/scheduler.js --job nightly - Simple watcher:
bun build/backend/jobs/scheduler.js --watch - Direct execution:
bun build/backend/jobs/<name>/index.js
- List metadata:
- Inspect the manifest to confirm the job metadata:
webstir backend-inspect --workspace "$PWD"
Implement the job
Jobs receive the same environment access and logging helpers as HTTP handlers. The scaffold exports a run() function you can extend:
// src/backend/jobs/cleanup/index.ts
import { createDatabaseClient } from '../../db/connection';
export async function run() {
const db = await createDatabaseClient();
const stale = await db.query('select id from sessions where expires_at < datetime("now")');
if (stale.length > 0) {
await db.execute('delete from sessions where id in (?)', [stale.map((row) => row.id)]);
console.info('[jobs] cleaned sessions', { count: stale.length });
} else {
console.info('[jobs] no sessions to clean');
}
await db.close();
}
.envvalues are loaded automatically; useprocess.env.NAMEor the helper exported from the template.- The scheduler prints job names, schedules, and manifest metadata so you can verify exactly what will run locally, and
--jsonemits the same metadata in a machine-friendly format for external schedulers.
Notes
- The CLI validates
--schedulestrings (@hourly,@daily,@weekly, cron-style fields,rate(...), or@reboot) but stores them exactly as provided so your production scheduler can interpret them. - On Bun
1.3.11+, the built-in watcher usesBun.cron.parse(...)for real cron expressions and nicknames such as0 0 * * *,*/15 * * * *,@daily, or@monthly, while still supportingrate(...)and@rebootfor local development loops. The manifest keeps the original schedule string unchanged, and--jsongives you a direct export path instead of scraping CLI text. - Jobs run through the scheduler automatically load
.envvalues, reuse the backend logger, and emit structured logs just like HTTP handlers. - Use
webstir backend-inspect --workspace "$PWD"after adding jobs to confirm the manifest entry (name, schedule, description, priority) before committing changes.
See Also
- CLI reference:
../reference/cli.md#add-job - Backend provider:
../explanations/solution.md(manifest ingestion)