{"id":50783399,"url":"https://github.com/igorgolovanov/nestjs-transactional","last_synced_at":"2026-06-12T05:04:18.544Z","repository":{"id":357003349,"uuid":"329677649","full_name":"igorgolovanov/nestjs-transactional","owner":"igorgolovanov","description":"Transactional, durable outbox, and CQRS integration for NestJS","archived":false,"fork":false,"pushed_at":"2026-05-21T13:08:38.000Z","size":1168,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-05-21T19:37:26.554Z","etag":null,"topics":["cqrs","event-driven","event-publication","kafka","microservices","nestjs","nestjs-cqrs","outbox-pattern","rabbitmq","saga-pattern","spring-modulith","transactional","transactions","typeorm","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/org/nestjs-transactional","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/igorgolovanov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/roadmap/README.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2021-01-14T16:51:21.000Z","updated_at":"2026-05-21T19:16:59.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/igorgolovanov/nestjs-transactional","commit_stats":null,"previous_names":["igorgolovanov/nestjs-transactional"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/igorgolovanov/nestjs-transactional","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorgolovanov%2Fnestjs-transactional","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorgolovanov%2Fnestjs-transactional/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorgolovanov%2Fnestjs-transactional/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorgolovanov%2Fnestjs-transactional/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/igorgolovanov","download_url":"https://codeload.github.com/igorgolovanov/nestjs-transactional/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/igorgolovanov%2Fnestjs-transactional/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34229688,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-12T02:00:06.859Z","response_time":109,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cqrs","event-driven","event-publication","kafka","microservices","nestjs","nestjs-cqrs","outbox-pattern","rabbitmq","saga-pattern","spring-modulith","transactional","transactions","typeorm","typescript"],"created_at":"2026-06-12T05:04:17.772Z","updated_at":"2026-06-12T05:04:18.535Z","avatar_url":"https://github.com/igorgolovanov.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @nestjs-transactional\n\n[![CI](https://github.com/igorgolovanov/nestjs-transactional/actions/workflows/ci.yml/badge.svg)](https://github.com/igorgolovanov/nestjs-transactional/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Node: 22+](https://img.shields.io/badge/node-%3E%3D22-brightgreen)](https://nodejs.org)\n[![TypeScript: 5.5+](https://img.shields.io/badge/typescript-5.5+-blue)](https://www.typescriptlang.org/)\n\n**Spring Modulith-equivalent transactional + event-delivery\ninfrastructure for NestJS.** Declarative `@Transactional` with every\npropagation mode, multi-datasource support, phase-aware event\nlisteners that integrate with `@nestjs/cqrs` `AggregateRoot`, and a\ndurable Event Publication Registry with retry, recovery, and\nat-least-once delivery semantics.\n\n## Packages\n\n| Package | npm | Purpose |\n| --- | --- | --- |\n| [`@nestjs-transactional/core`](packages/core) | [![npm](https://img.shields.io/npm/v/%40nestjs-transactional%2Fcore/alpha?label=npm)](https://www.npmjs.com/package/@nestjs-transactional/core) | AsyncLocalStorage context, `TransactionManager`, `@Transactional` decorator, adapter SPI |\n| [`@nestjs-transactional/typeorm`](packages/typeorm) | [![npm](https://img.shields.io/npm/v/%40nestjs-transactional%2Ftypeorm/alpha?label=npm)](https://www.npmjs.com/package/@nestjs-transactional/typeorm) | TypeORM adapter, transparent transactional repositories, multi-datasource support |\n| [`@nestjs-transactional/cqrs`](packages/cqrs) | [![npm](https://img.shields.io/npm/v/%40nestjs-transactional%2Fcqrs/alpha?label=npm)](https://www.npmjs.com/package/@nestjs-transactional/cqrs) | `@nestjs/cqrs` integration: handler wrapping, `@TransactionalEventsHandler`, `@IntegrationEventsHandler`, aggregate events |\n| [`@nestjs-transactional/outbox`](packages/outbox) | [![npm](https://img.shields.io/npm/v/%40nestjs-transactional%2Foutbox/alpha?label=npm)](https://www.npmjs.com/package/@nestjs-transactional/outbox) | Persistent Event Publication Registry — lifecycle states, async worker, staleness monitor, startup recovery, operator APIs, `@Externalized` SPI |\n| [`@nestjs-transactional/outbox-typeorm`](packages/outbox-typeorm) | [![npm](https://img.shields.io/npm/v/%40nestjs-transactional%2Foutbox-typeorm/alpha?label=npm)](https://www.npmjs.com/package/@nestjs-transactional/outbox-typeorm) | TypeORM persistence backend for the outbox — `event_publication` table, `FOR UPDATE SKIP LOCKED`, migration, dev-time auto-init |\n| [`@nestjs-transactional/outbox-microservices`](packages/outbox-microservices) | [![npm](https://img.shields.io/npm/v/%40nestjs-transactional%2Foutbox-microservices/alpha?label=npm)](https://www.npmjs.com/package/@nestjs-transactional/outbox-microservices) | Event externalization to message brokers via `@nestjs/microservices` `ClientProxy` (Kafka, RabbitMQ, NATS, JMS, gRPC, custom) |\n\n## Why?\n\nNestJS apps that talk to a database quickly grow a thicket of\n`dataSource.transaction(async em =\u003e ...)` blocks, repositories that\nthread `EntityManager` as an argument, and \"is this event fired after\nthe write is durable, or only if it is?\" doubt. Spring solved that\ndecades ago — this library brings the same ergonomics:\n\n```ts\n@Injectable()\nexport class OrderService {\n  constructor(\n    @InjectRepository(OrderRow)\n    private readonly orderRepo: Repository\u003cOrderRow\u003e,\n  ) {}\n\n  @Transactional()\n  async placeOrder(id: string): Promise\u003cvoid\u003e {\n    // The injected Repository auto-dispatches through the active\n    // @Transactional() scope's EntityManager. Every Repository in the\n    // call tree joins this transaction; rollback on throw, commit on\n    // resolve.\n    await this.orderRepo.save({ id, status: 'placed' });\n  }\n}\n```\n\n- **All seven Spring propagation modes**: `REQUIRED` (default),\n  `REQUIRES_NEW`, `NESTED` (savepoints), `SUPPORTS`, `NOT_SUPPORTED`,\n  `NEVER`, `MANDATORY`.\n- **Rollback rules** via `rollbackFor` / `noRollbackFor`.\n- **Multi-DataSource** as a first-class feature —\n  `@Transactional({ dataSource: 'billing' })` routes to the right\n  adapter; per-DS outbox stacks coexist without contention.\n- **Transparent transactional repositories** —\n  `@InjectRepository(Entity)`, `@InjectEntityManager`,\n  `@InjectDataSource` patterns automatically dispatch through the\n  active transaction. No `getCurrentEntityManager()` calls in user\n  service code.\n- **Phase-aware class-level event handlers** via\n  `@TransactionalEventsHandler`: `BEFORE_COMMIT`, `AFTER_COMMIT`,\n  `AFTER_ROLLBACK`, `AFTER_COMPLETION`. Matches `@nestjs/cqrs`\n  conventions (ADR-014).\n- **AggregateRoot integration** — `order.commit()` attaches events as\n  hooks on the current transaction; no more \"event published,\n  transaction rolled back\" race.\n- **Durable event delivery via the outbox pattern** — event\n  publications commit atomically with business writes; a background\n  worker delivers them at-least-once with automatic retry, staleness\n  detection, and startup recovery.\n- **`@IntegrationEventsHandler` as smart default** — Spring-Modulith-\n  equivalent class-level decorator. Durable via the outbox when wired,\n  in-memory fallback otherwise. Same source code, two delivery modes,\n  chosen by module wiring.\n\n## Quick start\n\n### Transactions only (TypeORM)\n\n```bash\npnpm add @nestjs-transactional/core @nestjs-transactional/typeorm\n```\n\n```ts\n// app.module.ts\nimport { Module } from '@nestjs/common';\nimport { TypeOrmModule } from '@nestjs/typeorm';\nimport { TransactionalModule } from '@nestjs-transactional/core';\nimport { TypeOrmTransactionalModule } from '@nestjs-transactional/typeorm';\n\n@Module({\n  imports: [\n    TypeOrmModule.forRoot({ /* your TypeORM config */ }),\n    TypeOrmModule.forFeature([OrderRow]),\n\n    TransactionalModule.forRoot({ isGlobal: true }),\n    TypeOrmTransactionalModule.forRoot(),\n  ],\n  providers: [OrderService],\n})\nexport class AppModule {}\n```\n\n```ts\n// order.service.ts\nimport { Injectable } from '@nestjs/common';\nimport { InjectRepository } from '@nestjs/typeorm';\nimport { Transactional } from '@nestjs-transactional/core';\nimport { Repository } from 'typeorm';\n\n@Injectable()\nexport class OrderService {\n  constructor(\n    @InjectRepository(OrderRow)\n    private readonly orderRepo: Repository\u003cOrderRow\u003e,\n  ) {}\n\n  @Transactional()\n  async placeOrder(id: string): Promise\u003cvoid\u003e {\n    // orderRepo.save(...) automatically dispatches through the\n    // active transaction — no getCurrentEntityManager boilerplate.\n    await this.orderRepo.save({ id, status: 'placed' });\n  }\n}\n```\n\n### Full stack with CQRS, outbox, and Postgres\n\n```bash\npnpm add @nestjs-transactional/core \\\n         @nestjs-transactional/typeorm \\\n         @nestjs-transactional/cqrs \\\n         @nestjs-transactional/outbox \\\n         @nestjs-transactional/outbox-typeorm\n```\n\n```ts\nimport {\n  OutboxEventPublisher,\n  OutboxListenerRegistry,\n  OutboxModule,\n  OutboxProcessingModule,\n} from '@nestjs-transactional/outbox';\nimport {\n  OutboxTypeOrmModule,\n  typeOrmEventPublicationRepositoryProvider,\n} from '@nestjs-transactional/outbox-typeorm';\nimport {\n  CqrsTransactionalModule,\n  OUTBOX_LISTENER_REGISTRAR,\n  OUTBOX_PUBLICATION_SCHEDULER,\n} from '@nestjs-transactional/cqrs';\n\n@Module({\n  imports: [\n    TypeOrmModule.forRoot({ /* your TypeORM config */ }),\n    TypeOrmModule.forFeature([OrderRow, EventPublicationEntity, EventPublicationArchiveEntity]),\n\n    TransactionalModule.forRoot({ isGlobal: true }),\n    TypeOrmTransactionalModule.forRoot(),\n\n    OutboxTypeOrmModule.forRoot({\n      schemaInitialization: { enabled: process.env.NODE_ENV !== 'production' },\n    }),\n\n    OutboxModule.forRoot({\n      repository: typeOrmEventPublicationRepositoryProvider(),\n      republishOnStartup: true,\n    }),\n    // Each feature module imports OutboxModule.forFeature([...]) for the\n    // event classes it owns — matches TypeOrmModule.forFeature() ergonomics.\n    OutboxModule.forFeature([OrderPlacedEvent]),\n\n    OutboxProcessingModule, // worker processes only\n\n    CqrsTransactionalModule.forRoot(),\n    // Do NOT import @nestjs/cqrs's CqrsModule directly — see Convention #6.\n  ],\n  providers: [\n    // Aggregate-root events flow through the outbox in addition to the\n    // in-memory dispatcher.\n    { provide: OUTBOX_PUBLICATION_SCHEDULER, useExisting: OutboxEventPublisher },\n    // @IntegrationEventsHandler classes route through the durable\n    // outbox path (rather than the in-memory fallback).\n    { provide: OUTBOX_LISTENER_REGISTRAR, useExisting: OutboxListenerRegistry },\n\n    PlaceOrderHandler,\n    InventoryReservationHandler,\n  ],\n})\nexport class AppModule {}\n```\n\n```ts\n@Injectable()\n@IntegrationEventsHandler(OrderPlacedEvent)\nexport class InventoryReservationHandler\n  implements IIntegrationEventHandler\u003cOrderPlacedEvent\u003e\n{\n  async handle(event: OrderPlacedEvent): Promise\u003cvoid\u003e {\n    // Durable. Runs in its own REQUIRES_NEW transaction after the\n    // publishing tx commits. Retries on failure. Resumes after a\n    // process restart.\n  }\n}\n```\n\nFor `forRootAsync`, multi-DataSource setups, externalization to a\nbroker, or graceful shutdown — see the\n[example library](#examples) below.\n\n## Roadmap\n\n| Phase | Status | Scope |\n| --- | --- | --- |\n| 0 — Monorepo setup | ✅ done | pnpm workspaces, TypeScript project refs, Jest, ESLint, Prettier, Changesets, CI |\n| 1 — `@nestjs-transactional/core` | ✅ done | Context, manager, propagation modes, decorator, interceptor, methods bootstrap, observability |\n| 2 — `@nestjs-transactional/typeorm` | ✅ done | Adapter, `getCurrentEntityManager`, multi-datasource, savepoints |\n| 3 — `@nestjs-transactional/cqrs` | ✅ done | Phase-aware dispatching, handler wrapping, `TransactionalEventPublisher`, `AggregateRoot` integration |\n| 4 — Examples \u0026 CI | ✅ done | Initial runnable examples, GitHub Actions, coverage reports |\n| 5 — `@nestjs-transactional/outbox` | ✅ done (alpha) | Types, SPI, registry, publisher, processor, staleness monitor, startup recovery, operator APIs, in-memory repo, NestJS modules |\n| 6 — `@nestjs-transactional/outbox-typeorm` | ✅ done (alpha) | Entity, repository, migration, `SchemaInitializer`, `OutboxTypeOrmModule` |\n| 7 — CQRS ↔ outbox integration | ✅ done (alpha) | `HybridEventPublisher`, `@IntegrationEventsHandler`, `IntegrationEventsHandlerScanner` with outbox/in-memory routing |\n| 8 — Testing utilities | ✅ done (alpha) | `PublishedEvents`, `AssertablePublishedEvents` in `/testing` subpath |\n| 9 — Documentation \u0026 release | ✅ done (alpha) | Architecture docs, ADRs, migration guide, full-stack examples, first `1.0.0-alpha.0` release shipped to npm |\n| 10 — Class-level handler API + naming refinement | ✅ done | Method-level → class-level migration (ADR-014); second pass renamed `@ApplicationModuleHandler` → `@IntegrationEventsHandler` |\n| 11 — Event externalization | ✅ done (alpha) | `EventExternalizer` SPI, `@Externalized` decorator, `outbox-microservices` package, ADR-015, ADR-016 (silent-success reliability finding), externalization example library coverage |\n| 14 — Multi-adapter architecture | ✅ done (alpha) | dataSource-name-keyed registration, multi-`forRoot` pattern (ADR-019), transparent transactional repositories, `OutboxTypeOrmModule` reshape, Tier 1–5 example library, ADR-018 |\n| *(future)* | 🗓 not scheduled | Broker-aware externalizers (native `kafkajs` / `amqplib` / `nats` under the same SPI for stricter delivery — see ADR-016), outbox-prisma, outbox-mongodb, OpenTelemetry, ESM dual packaging |\n\n## Examples\n\nA five-tier example library lives under [`examples/`](examples/) —\nsee [`examples/README.md`](examples/README.md) for the full\ncatalogue and the \"Picking the right starting point\" decision guide.\nQuick anchors:\n\n- **Tier 1 — Foundational**: [`basic-transactional`](examples/basic-transactional),\n  [`basic-outbox`](examples/basic-outbox),\n  [`basic-typeorm-outbox`](examples/basic-typeorm-outbox),\n  [`basic-cqrs`](examples/basic-cqrs).\n- **Tier 2 — Multi-DataSource**: [`multi-datasource-basic`](examples/multi-datasource-basic),\n  [`multi-datasource-outbox`](examples/multi-datasource-outbox),\n  [`multi-datasource-cqrs`](examples/multi-datasource-cqrs),\n  [`shared-database-modular-monolith`](examples/shared-database-modular-monolith).\n- **Tier 3 — Externalization**: [`externalization-kafka`](examples/externalization-kafka),\n  [`externalization-multi-broker`](examples/externalization-multi-broker),\n  [`externalization-multi-datasource`](examples/externalization-multi-datasource),\n  [`externalization-with-fallback`](examples/externalization-with-fallback).\n- **Tier 4 — Advanced patterns**: [`saga-pattern`](examples/saga-pattern),\n  [`audit-logging`](examples/audit-logging),\n  [`read-write-separation`](examples/read-write-separation),\n  [`testing-patterns`](examples/testing-patterns).\n- **Tier 5 — Production realism**: [`e-commerce-orders`](examples/e-commerce-orders),\n  [`async-config-from-environment`](examples/async-config-from-environment),\n  [`graceful-shutdown`](examples/graceful-shutdown).\n\n```bash\npnpm -C examples/basic-transactional start\n```\n\n## Documentation\n\n- **Per-package READMEs**: [`core`](packages/core/README.md),\n  [`typeorm`](packages/typeorm/README.md),\n  [`cqrs`](packages/cqrs/README.md),\n  [`outbox`](packages/outbox/README.md),\n  [`outbox-typeorm`](packages/outbox-typeorm/README.md),\n  [`outbox-microservices`](packages/outbox-microservices/README.md).\n- **Architecture overview**:\n  - [`docs/architecture/core-design.md`](docs/architecture/core-design.md) — core transaction infrastructure.\n  - [`docs/architecture/outbox-pattern.md`](docs/architecture/outbox-pattern.md) — the outbox pattern, lifecycle, performance.\n  - [`docs/architecture/outbox-integration-with-cqrs.md`](docs/architecture/outbox-integration-with-cqrs.md) — `HybridEventPublisher`, `@IntegrationEventsHandler`, handler flavours.\n  - [`docs/architecture/event-externalization.md`](docs/architecture/event-externalization.md) — `@Externalized` flow, sequence diagram, failure modes, reliability semantics.\n- **Architecture Decision Records**: [`docs/adr/`](docs/adr/) —\n  ADR-005 (method wrapping), ADR-006 (outbox rationale),\n  ADR-007 (outbox architecture), ADR-014 (class-level handler API),\n  ADR-015 (event externalization architecture),\n  ADR-016 (externalization reliability semantics),\n  ADR-018 (multi-adapter architecture),\n  ADR-019 (`OutboxModule` multi-`forRoot` pattern).\n- **Guides**: [`docs/guides/migrating-to-outbox.md`](docs/guides/migrating-to-outbox.md)\n  — step-by-step migration from `@TransactionalEventsHandler` to\n  durable delivery, plus multi-DataSource and externalization\n  walkthroughs.\n- **Implementation roadmap** (per-phase narrative):\n  [`docs/roadmap/README.md`](docs/roadmap/README.md).\n  **Empirically-discovered conventions** surfaced during\n  implementation: [`docs/status/conventions.md`](docs/status/conventions.md).\n- **Repository conventions** and PR workflow:\n  [`CONTRIBUTING.md`](CONTRIBUTING.md).\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for dev-environment setup,\ntesting, commit message style, and the changeset workflow.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figorgolovanov%2Fnestjs-transactional","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Figorgolovanov%2Fnestjs-transactional","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Figorgolovanov%2Fnestjs-transactional/lists"}