Skip to main content

TypeORM adapter

Declarative @Transactional() for NestJS + TypeORM. Keep @InjectRepository(Entity), add one decorator — transactions propagate through CLS (AsyncLocalStorage) across services. Standard NestJS dependency injection built on the actively maintained @nestjs-cls/transactional: no monkey-patching. Inspired by typeorm-transactional — a decorator-based approach many NestJS developers already know, but that is no longer maintained.

Install

npm install @nestjs-transactions/typeorm @nestjs-transactions/core \
@nestjs/typeorm typeorm @nestjs-cls/transactional \
@nestjs-cls/transactional-adapter-typeorm nestjs-cls

(All are peer dependencies — this package ships zero runtime dependencies. @nestjs/common and @nestjs/core are peers too, but every NestJS app already has them.)

Quick start

Use NestjsTypeormModule from this package instead of @nestjs/typeorm's TypeOrmModule — one unified module owns both the database connection and transaction propagation:

// app.module.ts
import { NestjsTypeormModule } from '@nestjs-transactions/typeorm';

@Module({
imports: [NestjsTypeormModule.forRoot({/* all @nestjs/typeorm options ... */})],
})
export class AppModule {}

forRoot() accepts everything @nestjs/typeorm's does (autoLoadEntities, retryAttempts, name, …) — it delegates DataSource creation to @nestjs/typeorm internally — plus the transactional options defaultTxOptions and enableTransactionProxy. It also registers the @nestjs-cls/transactional CLS plugin that powers @Transactional() (starting/committing/rolling back transactions and swapping the active EntityManager).

// member.module.ts — same shape as @nestjs/typeorm's forFeature
@Module({
imports: [NestjsTypeormModule.forFeature([Member])],
providers: [MemberService, AccountingService],
})
export class MemberModule {}
// member.service.ts — completely vanilla NestJS + TypeORM
// (InjectRepository is re-exported — @nestjs/typeorm's symbol, one import)
import { InjectRepository, Transactional } from '@nestjs-transactions/typeorm';

@Injectable()
export class MemberService {
constructor(
@InjectRepository(Member) private readonly repo: Repository<Member>,
private readonly accounting: AccountingService,
) {}

@Transactional()
async register(name: string) {
const member = await this.repo.save({ name });
await this.accounting.openAccount(member); // joins the SAME transaction —
return member; // no decorator needed there
}
}

If register throws, everything rolls back — including writes made in AccountingService. Outside a transaction the repository behaves like a plain TypeORM repository.

How it works

forFeature([Member]) registers a provider under TypeORM's standard repository token — the exact token @InjectRepository resolves — whose value is a lazy proxy over txHost.tx.getRepository(Member). txHost.tx is the transactional EntityManager inside @Transactional() and the regular one outside. No prototypes are patched; it is ordinary NestJS dependency injection. See Concepts for the full picture.

On this section