A progressive Node.js framework for building efficient and scalable server-side applications.
Transactional outbox module for Nest: messages written in the same database transaction as your business data, a relay that publishes them after commit with leases, retries and a dead-letter table, and an inbox that deduplicates redeliveries, with no third-party dependencies.
$ npm i --save @nestjs/outbox@nestjs/outbox/postgres ships PostgresOutboxStore, which keeps the messages, the dead letters and the consumers' inbox in a schema of its own (nest_outbox) and writes through the client your application already uses: fromPg(pool), fromDrizzle(db), fromTypeOrm(dataSource), fromPrisma(prisma) or fromKysely(db). Register it with a factory provider:
import { OutboxStorage } from '@nestjs/outbox';
import { fromDrizzle, PostgresOutboxStore } from '@nestjs/outbox/postgres';
@Module({
imports: [DrizzleModule.forRoot({ drizzle, connection: process.env.DATABASE_URL! }), OutboxModule.forRoot()],
providers: [
{
provide: PostgresOutboxStore,
inject: [getDrizzleToken(), OutboxStorage],
useFactory: (db: Database, storage: OutboxStorage) => new PostgresOutboxStore({ executor: fromDrizzle(db) }, storage),
},
],
})
export class AppModule {}outbox.add(tx, message) then takes your ORM's own transaction object (Drizzle's tx, a TypeORM EntityManager, a Prisma transaction client, a Kysely Transaction, a pg client after BEGIN). The store applies its migrations at startup, except when NODE_ENV is production; there, apply them on deploy with npx nest-outbox migrate --url <database url> (status checks, sql prints them for your own migration tool).
@nestjs/outbox/mysql ships MySqlOutboxStore (MySQL 8.4 LTS and 9.x), registered the same way: import it and the executor from @nestjs/outbox/mysql instead (fromMysql2(pool), fromDrizzle(db), fromTypeOrm(dataSource), fromPrisma(prisma) with @prisma/adapter-mariadb, or fromKysely(db)). Its tables live in your connection's database, named after the schema option (nest_outbox_messages, nest_outbox_dead_letters, nest_outbox_inbox); ids, topics, keys and consumer names are compared byte for byte and hold at most 255 characters. npx nest-outbox migrate --url mysql://... applies its migrations, and sql --dialect mysql prints them (MySqlOutboxStore.migrationStatements() lists them one per string, for TypeORM's queryRunner.query()).
The consumers' inbox records (one per consumer per message) and the dead letters are kept until your application deletes them: nothing in the module does it for you. On the PostgreSQL and MySQL stores the tables grow without limit; on the default in-memory store they grow on the heap (about 470 bytes per inbox record, so 200k records take roughly 94 MB, and each dead letter keeps its full payload and attempt history), which adds up in a long-running dev server or with allowInMemoryStorage: true. Schedule OutboxInbox.prune(olderThan), and purge the dead letters you've handled (requeued or given up on) with OutboxDeadLetters.purge(target), e.g. with @nestjs/schedule:
import { Cron, CronExpression } from '@nestjs/schedule';
import { OutboxDeadLetters, OutboxInbox } from '@nestjs/outbox';
@Injectable()
export class OutboxRetention {
constructor(private readonly inbox: OutboxInbox, private readonly deadLetters: OutboxDeadLetters) {}
@Cron(CronExpression.EVERY_DAY_AT_3AM)
async prune() {
await this.deadLetters.purge({ failedBefore: new Date(Date.now() - 14 * 86_400_000) });
await this.inbox.prune('30d');
}
}The inbox record is what recognizes a redelivery, so keep it longer than a message can take to come back: the whole retry schedule, your broker's redelivery window for remote consumers, and, above all, the longest a dead letter may wait before someone requeues it, since a requeued message keeps its id. Purging dead letters after 14 days bounds that wait, and 30 days of inbox covers it. Without @nestjs/schedule, a setInterval() started in onModuleInit() and cleared in onModuleDestroy() does the same.
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please read more here.
- Author - Kamil Myśliwiec
- Website - https://nestjs.com
- Twitter - @nestframework
Nest is MIT licensed.