Samples
00-showcase
sample/00-showcase
is a runnable, end-to-end demonstration of the whole pattern on SQLite and
the in-process transport — the no-broker default profile — with no migration
step (it creates the tables inline so it runs as a single script):
schema.ts— the library'soutboxEvents/inboxEventsfactories combined with the businessorderstable and anorder_audittable (the consumer's observable side effect).app.module.ts— a global Drizzle module,ClsModule.forRootwith the Drizzle transactional adapter (enableTransactionProxy: true), andMessagingModule.forRootwithSqliteOutboxStore,SqliteInboxStore, and anInProcessOutboxTransportover a sharedOutboxRegistry.order.service.ts—placeOrderinserts the order row andenqueues theorder.placedevent in the same@Transactional()method. The payload is a plain interface —enqueueaccepts it without casts.order-placed.handler.ts— the consumer: it registers itself fororder.placedon module init and pairs withInboxService.runOnce(keyed onidempotencyKey ?? id) so the audit row is written exactly once.scripts/smoke.ts— drives the flow and asserts each guarantee.
What it proves
The smoke script asserts the properties that make the pattern correct:
- Atomic outbox — after
placeOrder, both theordersrow and exactly oneoutbox_eventsrow exist. They committed in the same transaction, so there is no event without the work and no work without the event. - Claim and dispatch — one
OutboxClaimer.tick()delivers the committed event to the registered handler and marks the row completed (report.completed === 1). In production with a broker, the Kafka transport publishes instead — same seam, different transport. - Exactly-once inbox — the handler's first delivery writes one
order_auditrow; replaying the same logical event (delivery is at-least-once) is deduplicated by the inbox and writes no second row. - Unroutable events fail fast — an event on a topic with no registered
handler maps to
PermanentError, so the claimer fails the row immediately instead of retrying forever.
On success it prints:
Showcase smoke passed: atomic outbox → in-process dispatch → exactly-once inbox.
Running it
From the repository root:
npm install
npm run showcase
The showcase runs the in-process default profile — no Docker, no Kafka. Rebind
KafkaOutboxTransport and a thin @KafkaConsumer (see the
Quick Start) to take the same flow to production.
01-kafka
sample/01-kafka
takes the showcase one step further: it drives the whole pair over the real
Kafka transport — KafkaOutboxTransport on the producer side and an actual
@KafkaConsumer delegating to KafkaInboxConsumer on the consumer side — using
@nest-native/kafka's
in-memory broker (KafkaTestModule), so it still runs with no cluster.
app.module.ts— wiresKafkaTestModule.forRoot()andMessagingModule.forRootAsync({ ..., useTransport: (producer) => new KafkaOutboxTransport(producer) }).order.consumer.ts— a thin@KafkaConsumer('order.placed')that delegates to the library'sKafkaInboxConsumer.consume(...), supplying the payload validator and the exactly-once side effect.scripts/smoke.ts— places an order, runsOutboxClaimer.tick()(which publishes through Kafka to the consumer), asserts one audit row, then re-emits the same message to prove the inbox deduplicates the redelivery.
Run it from the repository root with npm run sample:focused. This is the closest
you can get to the production path without a broker; point KafkaTestModule at a
real cluster (or use KafkaModule) and the same code runs unchanged.