@nest-native/messaging
v0.3.1
Published
Transactional outbox + idempotent inbox for NestJS, persisted with Drizzle ORM (SQLite, Postgres + MySQL), with a Kafka transport
Maintainers
Readme
@nest-native/messaging
[!NOTE] v0.x — early but stable. The producer, claimer, inbox, transport seam, and the Drizzle stores are implemented and tested at 100% coverage. SQLite, Postgres, and MySQL are supported, with in-process (no broker) and Kafka transports.
The problem it solves
"Write rows and publish an event" is a dual write — two systems that can't be updated atomically. If the process crashes between the DB commit and the broker publish, the event is lost; publish-then-fail-to-commit emits a phantom event.
@nest-native/messaging closes that gap with the two halves of the reliable-messaging pattern:
- Transactional outbox (producer) —
enqueue()writes the event into anoutbox_eventsrow inside your business transaction (via@nestjs-cls/transactional). A background claimer then relays committed rows to the broker — at-least-once, with retry/backoff. - Idempotent inbox (consumer) —
runOnce()deduplicates redeliveries via a unique(source, message_key)row written in the same transaction as the side effect, yielding effective exactly-once processing.
It is not a generic multi-broker abstraction — it is the outbox/inbox pattern, done natively for the Drizzle + Kafka + NestJS stack.
Install
npm install @nest-native/messaging
# plus your driver + transport (peer dependencies):
npm install drizzle-orm @nestjs-cls/transactional better-sqlite3 # or pg / mysql2
npm install @nest-native/kafka # only for the Kafka transportEntry points
| Import | Contents |
| --- | --- |
| @nest-native/messaging | core engine — OutboxProducer, OutboxClaimer + worker loop, InboxService, the OutboxTransport/OutboxStore/InboxStore seams, the wire contract, MessagingModule |
| @nest-native/messaging/in-process | the no-broker default transport — OutboxRegistry (topic → handler) + InProcessOutboxTransport |
| @nest-native/messaging/sqlite | better-sqlite3 (synchronous) stores + outbox_events/inbox_events table factories |
| @nest-native/messaging/postgres | node-postgres (async) stores + table factories |
| @nest-native/messaging/mysql | mysql2 (async) stores + table factories |
| @nest-native/messaging/kafka | KafkaOutboxTransport + the idempotent consumer engine, over @nest-native/kafka |
| @nest-native/messaging/testing | in-memory transport for broker-free tests |
How it fits together
- Add the dialect's table factories to your Drizzle schema and generate a migration.
- Configure
@nestjs-cls/transactionalwith the Drizzle adapter, then registerMessagingModule.forRoot({ drizzleInstanceToken, outboxStore, inboxStore, transport }). - Inject
OutboxProducerinto your@Transactional()services andenqueue()alongside your business writes. - Run
OutboxClaimerin a worker (runWorkerLoop) to relay events through the transport — in-process handlers by default, Kafka when a broker enters the picture. - Consume in-process by registering a handler per topic on the
OutboxRegistry, or over Kafka with a thin@KafkaConsumerthat delegates to the idempotent consumer engine. Delivery is at-least-once either way — make handlers idempotent or pair them with the inbox.
See the 00-showcase sample for a runnable end-to-end example on SQLite.
Status & scope
- Drivers: SQLite (better-sqlite3, sync), Postgres (
pg, async), and MySQL (mysql2, async) via per-dialect stores. - Transports: in-process (default,
@nest-native/messaging/in-process— no broker, at-least-once via the claimer) and Kafka (@nest-native/kafka). - Roadmap: additional transports. CDC (Debezium) is an intentional non-goal — this is the app-level outbox.
Part of the nest-native family. Not affiliated with the NestJS core team. MIT licensed.
