@startupjs/worker
Run background jobs in StartupJS with a simple API.
This package is useful when work should not block a request lifecycle, for example:
- sending emails or push notifications
- generating reports
- syncing data with external APIs
- recurring background tasks (cron)
- high-priority tasks that should be processed separately
What You Usually Need
After installing @startupjs/worker, most projects only need 2 things:
- Define jobs in
workerJobs/*.js. - Call
await runJob(name, data)from your app code.
The worker plugin is enabled by default, and worker auto-start is enabled by default.
runJob() always waits for completion and returns the job result.
If the job throws, runJob() throws too, so try/catch works as expected.
data can be any JSON-serializable value (object, string, number, array, boolean, null).
Quick Start
1) Install
2) Create a job file
3) Run the job
Production Deployment (Important)
Default behavior:
- app server process also runs workers
- this is fine for local/dev and simple deployments
Production recommendation for larger setups:
- run a dedicated worker microservice with
npx startupjs start-worker-production - disable worker auto-start in your app server production process
Use autoStartProduction: false in app server config:
Then run workers separately:
This command starts workers even in production mode, because it initializes workers directly.
Cron Jobs
Cron jobs are for tasks that must run on a schedule (for example cleanup, sync, or periodic reports).
Use cron when:
- the job should run automatically at fixed times
- you do not want to trigger it manually from app code each time
Important:
- cron does not replace manual runs
- you can still run the same job anytime with
runJob(...) cron.datais the input payload passed to the job for scheduled runs
API:
export const cron = '<cron pattern>'- or
export const cron = { pattern: '<cron pattern>', data: <any serializable value> }
Example without data:
Example with data:
The scheduled run above is equivalent to calling:
Singleton Jobs
Singleton jobs prevent duplicate work.
Use singleton when:
- the same job should not run multiple times in parallel
- duplicate requests can happen and should collapse into one execution
There are two singleton modes:
- global singleton: one in-flight job for that job name
- keyed singleton: one in-flight job per key
What keyed singleton means:
- you return a key from
singleton(data) - jobs with the same key are treated as duplicates
- jobs with different keys can still run in parallel
Why keyed singleton is useful:
- avoid duplicate work for one entity (user, org, document)
- still keep concurrency across different entities
API:
export const singleton = truefor one global singleton per job nameexport const singleton = (data) => valuefor keyed singleton (for example per user)
Global singleton example:
Keyed singleton example (per user):
In this example:
rebuildUserFeed({ userId: 'u1' })+rebuildUserFeed({ userId: 'u1' })collapse into one executionrebuildUserFeed({ userId: 'u1' })andrebuildUserFeed({ userId: 'u2' })can run in parallel
In keyed mode, the returned value must be JSON-serializable.
Job File API
Each file in workerJobs should export:
default: required async/sync functioncron: optional cron configworker: optional worker name (defaultorpriority)singleton: optional deduplication config
Job Context (default export second argument)
Your handler receives:
log(message, { data, err })log.warn(message, { data, err })log.error(message, { data, err })job(raw queue job object)
Use log(...) instead of console.log(...) when you want logs visible in the queue dashboard.
Examples By Use Case
1) Simple async background task
Why: move slow work out of request handlers.
2) Cron job
Why: run periodic maintenance tasks automatically.
With data payload:
3) Priority queue job
Why: keep urgent tasks separate from regular background load.
4) Singleton job (global)
Why: prevent duplicate executions of the same job type.
5) Singleton job by key (for example userId)
Why: allow parallel work for different users, but dedupe per user.
6) Per-call timeout override
Why: most jobs use a global timeout, but some calls need a custom value.
7) Add jobs from another plugin
Why: reusable modules can contribute their own jobs.
Startup and Configuration
Server-side worker options:
Option meanings:
autoStart(trueby default): auto-initialize workers on server startup.autoStartProduction(trueby default): iffalse, workers do not auto-start whenNODE_ENV=production.concurrency(300): per-worker concurrency.jobTimeout(30000ms): default timeout for jobs.useSeparateProcess(false): execute job handlers in sandboxed child runner.useWorkerThreads(true): when sandbox is on, use worker threads.dashboard: queue UI settings. Providerouteto enable the dashboard route.
Worker Topology (default + priority)
The package has two fixed worker names:
defaultpriority
By default both start.
To start only specific workers, use WORKERS env var (comma-separated):
This enables setups like:
- app servers enqueue jobs
- one worker service handles both queues
- another dedicated worker service handles only
priority
Common commands:
If you run dedicated worker services in all environments, set autoStart: false.
If you split only in production, set autoStartProduction: false so dev/stage behavior stays unchanged.
Public Exports
@startupjs/worker:runJob(name, data?, options?)@startupjs/worker/init:initWorker(options?)@startupjs/worker/plugin: StartupJS plugin export used by plugin registry
Error Handling and Results
runJobreturns the value returned by your job handler.- If your job throws,
runJobthrows. - If the job does not exist,
runJobthrows with a list of available jobs. - If a job declares unknown
worker, initialization fails and lists supported workers.
Timeout Behavior
- Timeouts are enforced as promise timeouts for all modes.
- In same-process mode, timed-out code cannot be force-killed; execution may continue in background.
- In separate-process mode, timeout can terminate the sandbox runner for harder isolation.
Technical Details (Optional)
Under the hood this package uses BullMQ, but you usually do not need to care.
Implementation summary:
- Jobs are discovered from
workerJobs/*.js. - Additional jobs can be injected through StartupJS
workerJobsplugin hook. initWorkerstarts workers, syncs cron schedulers, and removes stale schedulers.runJobenqueues a job and waits for completion before returning.- Queue names are fixed to
defaultandpriority.