Prisma adapter
Declarative @Transactional() for NestJS + Prisma. Inject one
transaction-aware Prisma client, add one decorator, and drop the $transaction
boilerplate — transactions propagate through CLS (AsyncLocalStorage) across
services. Standard NestJS dependency injection built on the actively maintained
@nestjs-cls/transactional:
no monkey-patching. The same decorator-based ergonomics the
TypeORM adapter offers, for Prisma.
Install
npm install @nestjs-transactions/prisma @nestjs-transactions/core \
@prisma/client @nestjs-cls/transactional \
@nestjs-cls/transactional-adapter-prisma 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; you likely already have @prisma/client as well.)
Quick start
Provide your Prisma client the canonical NestJS way:
// prisma.module.ts
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
@Module({ providers: [PrismaService], exports: [PrismaService] })
export class PrismaModule {}
Prisma 7 requires a driver adapter. Pass it to the PrismaClient constructor
exactly as you would without this package — nothing here changes:
import { PrismaPg } from '@prisma/adapter-pg';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
constructor() {
super({ adapter: new PrismaPg({ connectionString }) });
}
async onModuleInit() {
await this.$connect();
}
}
Register the transactional module once at the app root:
// app.module.ts
import { TransactionalModule } from '@nestjs-transactions/prisma';
@Module({
imports: [
PrismaModule,
TransactionalModule.forRoot({
prismaToken: PrismaService,
sqlFlavor: 'postgresql', // enables Propagation.NESTED (savepoints)
imports: [PrismaModule],
}),
],
})
export class AppModule {}
What each option does:
prismaToken(required) — the DI token yourPrismaClient/PrismaServiceis provided under. It can be any token, including a string token holding an$extends-ed client.imports— the module(s) that export the client underprismaToken, so the adapter can resolve it (here,PrismaModule— note itexports: [PrismaService]).sqlFlavor— required only forPropagation.NESTED, which emulates savepoints with raw SQL (see Propagation).defaultTxOptions— default$transactionoptions (timeout,maxWait,isolationLevel) for every transaction (see Transaction options).
TransactionalModule.forRoot() registers the @nestjs-cls/transactional CLS
plugin that powers @Transactional(); it does not create a connection — it
resolves the client you already provide. Unlike the TypeORM adapter, there is
no forFeature: Prisma has no per-entity registration, so one forRoot() is
the whole setup.
Then inject the transaction-aware client anywhere:
// user.service.ts
import {
InjectPrismaClient,
Transactional,
runOnTransactionCommit,
} from '@nestjs-transactions/prisma';
import { Prisma } from '@prisma/client';
@Injectable()
export class UserService {
constructor(
@InjectPrismaClient() private readonly prisma: Prisma.TransactionClient,
private readonly audit: AuditService,
) {}
@Transactional()
async signUp(email: string) {
const user = await this.prisma.user.create({ data: { email } });
await this.audit.record(user.id); // joins the SAME transaction — no decorator needed there
runOnTransactionCommit(() => this.mailer.sendWelcome(email)); // only after COMMIT
return user;
}
}
If signUp throws, everything rolls back — including writes made in
AuditService. Outside a transaction the injected client behaves like the plain
base client. Calls to other @Transactional() methods join the same transaction
by default (Propagation.REQUIRED).
How it works
@InjectPrismaClient() resolves a provider whose value is a lazy proxy over
txHost.tx (core's createTransactionAwareProxy). txHost.tx is the active
interactive-transaction client inside a @Transactional() method and the base
client outside it. Because the proxy re-resolves on every property access, one
injected client silently follows the current transaction. No prototypes are
patched; it is ordinary NestJS dependency injection. See
Concepts.
On this section
- Propagation — the seven propagation modes and the
NESTED/sqlFlavorrule. - Transaction options —
timeout,maxWait,isolationLevel. - Multiple connections — named clients.
- Transaction hooks — run code after commit/rollback.
- Programmatic control —
TransactionHostand@InjectTransaction(). - Testing — the no-op module for unit tests.
- Caveats.