Background jobs for NestJS without Redis — a Drizzle-backed job queue (SQLite, Postgres & MySQL) with transactional enqueue, retries, delayed and unique jobs.
Note
v0.x — early but stable. The producer, claimer, handler discovery, and the three Drizzle stores are implemented and tested at 100% coverage. SQLite, Postgres, and MySQL are supported.
Most NestJS apps grow a first background job long before they need a queueing system: send a welcome email after signup, generate a report, retry a flaky webhook. The official NestJS answer is @nestjs/bullmq — which means operating Redis for what is often a handful of jobs a minute. And because Redis is a second system, the classic dual-write bug appears on day one: the signup commits but the process crashes before queue.add() — the email is never sent. Or queue.add() succeeds and the transaction rolls back — a welcome email for a user that does not exist.
@nest-native/jobs stores jobs in the same Drizzle database your app already has:
- Transactional enqueue —
enqueue()inserts the job row inside your business transaction (via@nestjs-cls/transactional). The job exists if and only if your writes committed. - Nest-native execution — declare a class with
@JobHandler('email.welcome'), register it as a provider, and the claimer dispatches to it with full DI. Handlers are discovered at bootstrap; duplicate names throw at startup. - Retries, delays, priorities, unique jobs — jittered exponential backoff (or
RetryableError's explicitdelayMs),PermanentErrorto fail fast,runAt/delayMsscheduling,priorityordering, anduniqueKeydedup among active jobs. - Zero runtime dependencies — everything (Nest, Drizzle, your driver) is a peer you already installed.
npm install @nest-native/jobs
# plus your driver (peer dependencies):
npm install drizzle-orm @nestjs-cls/transactional better-sqlite3 # or pg / mysql2| Import | Contents |
|---|---|
@nest-native/jobs |
core engine — JobsService (enqueue), JobsClaimer + runWorkerLoop, @JobHandler + JobsHandlerExplorer, RetryableError/PermanentError, the JobStore seam, JobsModule |
@nest-native/jobs/sqlite |
better-sqlite3 (synchronous) store + the jobs table definition |
@nest-native/jobs/postgres |
node-postgres (async) store + table definition |
@nest-native/jobs/mysql |
mysql2 (async) store + table definition |
@nest-native/jobs/testing |
drainJobs + RecordingJobHandler for hermetic tests |
- Add the dialect's
jobstable definition to your Drizzle schema and generate a migration with drizzle-kit. - Configure
@nestjs-cls/transactionalwith the Drizzle adapter, then registerJobsModule.forRoot({ drizzleInstanceToken, store }). - Inject
JobsServiceinto your@Transactional()services andenqueue()alongside your business writes — the job commits atomically with them. - Declare
@JobHandler('job.name')classes as providers; the explorer builds the name → handler registry at bootstrap. - Run
JobsClaimerin a worker (runWorkerLoop) — it claims due jobs in batches (priority first, oldest due first, reclaiming stuck ones) and dispatches to your handlers. Delivery is at-least-once — make handlers idempotent or key side effects onctx.jobId.
See the 00-showcase sample for a runnable end-to-end example on SQLite, and the documentation for the full guide.
BullMQ (@nestjs/bullmq) |
pg-boss | @nest-native/jobs |
|
|---|---|---|---|
| Backing store | Redis (required) | Postgres only | the Drizzle DB you already run — SQLite, Postgres, or MySQL |
| NestJS integration | official wrapper module | none (framework-agnostic) | native — module, DI, @JobHandler decorators |
| Enqueue in your DB transaction | no (Redis is a second system) | yes (raw SQL in your tx) | yes — first-class, via @nestjs-cls/transactional |
| Delivery | Redis push (blocking ops) | polling + LISTEN/NOTIFY | polling claimer |
| Throughput | very high | high | right-sized — polling batches, fine for most apps' background work |
| Repeatable / cron jobs | yes | yes | no (non-goal — use @nestjs/schedule) |
| Dashboards, rate limiting | yes | partial | no |
| Runtime dependencies | Redis server + client | pg |
zero (peers you already have) |
If you need tens of thousands of jobs per second, sandboxed processors, or a dashboard, use BullMQ — it is excellent at that. If you run Postgres without Nest, pg-boss is battle-tested. This library is for the large middle: NestJS + Drizzle apps that want reliable background jobs without operating another system.
Every PR runs the full gate — build, typecheck, coverage with c8 enforced at
100% for statements, branches, functions, and lines, cognitive complexity
enforcement (SonarJS threshold 15), tarball validation, sample version sync,
and a supply-chain audit:
npm run ciTwo optional, local-only layers sit on top (neither is required to contribute, and forks work without Docker):
- Full mode —
npm run infra:up && npm run test:fullruns the gated MySQL round-trip spec against a disposable Docker container (compose.yaml);npm run infra:downcleans up. - Mutation testing —
npm run test:mutation(incremental Stryker run;test:mutation:fullre-tests everything). Scope withSTRYKER_MUTATE, include the gated MySQL spec withSTRYKER_WITH_INFRA=1. Never runs in CI.
Details — including the pre-PR ritual and agent instructions — in GUIDELINES_NEST_JOBS.md.
- Cron / repeatable jobs —
@nestjs/schedulealready does this well; combine it withenqueue()if you want scheduled work to flow through the queue. - Dashboards / UI, rate limiting, concurrency groups.
- LISTEN/NOTIFY push — the claimer polls;
pollIntervalMsis your latency knob. - Redis-class throughput — this is a polling claimer over your relational DB, by design.
Part of the nest-native family. Not affiliated with the NestJS core team. MIT licensed.