Aviary

Comparison

How nestjs-durable compares to Temporal, Inngest, and BullMQ — what the suspend-model library approach buys you, and when a dedicated orchestration cluster or a managed platform is the better call.

Durable execution is a crowded space, and the honest answer to "which one?" is it depends on your architecture. This page positions nestjs-durable against the three tools people most often weigh it against — as fairly as we can manage.

The short version: nestjs-durable is a library, not a platform. It lives inside your NestJS app, persists state to the SQL database you already run through the ORM you already use, and dispatches steps over the queue you already have. There is no extra server to deploy, no vendor to sign up with, and no new language runtime — the trade being that you own scaling and operations, the way you already do for the rest of your app.

At a glance

nestjs-durableTemporalInngestBullMQ
ShapeNestJS libraryOrchestration cluster + SDKsManaged platform (or self-host)Queue library
Extra infranone — your DB + optional queueTemporal server + its DBInngest server / cloudRedis
State lives inyour Postgres/MySQL/SQLiteTemporal's persistenceInngest's storeRedis
Workflow modelreplay + suspend, plain async codereplay, plain codestep memoization over HTTPjobs + flows (no replay)
Waits (sleep/signal)zero-compute suspendzero-computezero-computedelayed jobs only
Sagas (compensation)built-in — { compensate } on any stepmanual try/catch patternmanual
Event triggersonEvent + debounce/batchschedules; signal-with-start by handcore feature (debounce, batch)
Per-key mutexsingleton (FIFO per key)one run per workflow idconcurrency key = 1Pro only (groups)
Durable entitiesbuilt-in (@Entity/@On)
Cross-languageTS + Python workers, one workflowper-language SDKsHTTP (any language)JS/TS only
DI integrationnative NestJS providersmanualframework-agnosticmanual
Dashboardbuilt inTemporal WebInngest dashboardthird-party (Taskforce, Bull Board)
Scale ceilingyour DB's write throughputvery high (dedicated cluster)managed for youRedis throughput

vs Temporal

Temporal is the reference implementation of durable execution, and its model — deterministic replay of plain code — is the same one this library uses. If you've written a Temporal workflow, ctx.step / ctx.sleep / signals / ctx.patched will all feel familiar; even the query/update split is deliberately Temporal-shaped. A few things Temporal leaves as hand-rolled patterns are first-class here: saga compensation is a { compensate } option on any step (undone in reverse, durably) instead of a try/catch you write yourself, and durable entities — keyed, serialized actors — have no Temporal equivalent at all.

The difference is operational. Temporal is a separate orchestration cluster: its own server, its own database, its own deployment lifecycle, workers connecting over gRPC. That buys enormous scale, multi-region namespaces, and battle-tested guarantees — and costs you a second distributed system to run (or a Temporal Cloud bill). nestjs-durable inverts the trade: the engine is a module in your app, state is rows in your existing database, and "deploying the orchestrator" is just deploying your app.

Choose Temporal when workflow orchestration is a core, high-scale concern — millions of concurrent runs, many teams, many languages — and a dedicated cluster earns its keep. Choose nestjs-durable when you're a NestJS shop that wants durable checkout/onboarding/pipeline flows without adopting and operating a second platform.

vs Inngest

Inngest attacks the same problem from the serverless direction: your steps are memoized function invocations driven over HTTP by the Inngest server, which makes it a natural fit for Vercel/Netlify-style deployments and event-driven fan-out. It is excellent at what it does, and its managed offering removes the ops question entirely.

The differences that matter here: nestjs-durable workflows are long-lived class instances with NestJS dependency injection — steps are provider methods, tested like any provider — and the suspend model parks a waiting run as a database row rather than re-invoking a function per step. State stays in your database (a hard requirement in regulated or air-gapped environments), and step dispatch rides your own transport (in-process, BullMQ, SQS, or plain DB polling) instead of inbound HTTP. Inngest's signature event ergonomics have direct analogues too: @Workflow({ onEvent, debounce, batch }) starts workflows from published events with the same coalescing patterns ("reindex at most once a minute", "process uploads in batches").

Choose Inngest for serverless/edge architectures or when you want a managed platform with event-driven ergonomics. Choose nestjs-durable for long-running NestJS services, DI-first codebases, and data-residency constraints.

vs BullMQ

This is the comparison that matters most in practice, because BullMQ is what most NestJS apps already use — and this library runs happily on top of BullMQ as its production transport.

BullMQ gives you jobs and queues: retries, priorities, rate limits, delayed jobs, and flows for parent/child trees. What it does not give you is a durable function: there is no replay, so a multi-step process must be modeled as a chain of independent job handlers passing state through job data. The "what happened so far" lives implicitly across queues; a crash between jobs leaves you reconstructing progress; waiting on a human approval means inventing a persistence scheme for the half-finished flow.

nestjs-durable keeps BullMQ underneath for what it's great at — moving step invocations between processes — and adds the durable layer on top: the whole flow is one readable function, every step is checkpointed, waits are await ctx.sleep(...) / await ctx.waitForSignal(...) that survive deploys, and one dashboard shows the end-to-end run.

Choose plain BullMQ for fire-and-forget background jobs — a resize, an email, a webhook delivery — where each job is independent and "workflow" would be overkill. Choose nestjs-durable the moment a process has steps that must not re-run, waits, or state that outlives a deploy.

Where nestjs-durable is honestly weaker

  • Scale ceiling. The engine writes checkpoints to your SQL database. That's a feature (one source of truth, your backup story) until your run volume makes checkpoint writes a hot spot — a dedicated cluster like Temporal moves that ceiling far higher.
  • Ecosystem maturity. Temporal has a decade of production hardening and a large community; this library is younger and NestJS-scoped by design.
  • Languages. Cross-language support today means TypeScript workflows with Python workers (or Python-authored workflows over the same protocol) — not the per-language SDK breadth of Temporal.

If those are your constraints, use the bigger tool. If they aren't, skipping a second platform is a real simplification — in code, in ops, and in the audit story.

On this page