nnestjs-drizzle-crud
Guides

Transactions

Use executeSqlTransaction() to span multiple service calls in one atomic unit.

For operations that span multiple services (or multiple calls to the same service), the package exposes executeSqlTransaction() for explicit transaction control.

The helper

const  = new ({} as any);
.(new ({} as any));

executeSqlTransaction opens a transaction on the configured database, runs the callback, commits on success, and rolls back on any thrown error.

Passing { transaction: tx } to methods

Every CRUD method accepts an optional last argument of SqlOperationOptions. Pass { transaction: tx } to enlist the call in an existing transaction:

const tx = await users.executeSqlTransaction(async (tx) => {
  // both writes share the same transaction
  const user = await users.create({ name: 'Ada' }, { transaction: tx });
  const profile = await profiles.create({ userId: user.id }, { transaction: tx });
  return { user, profile };
});

The tx handle is a Drizzle transaction object. It's typed as any in SqlOperationOptions so the package doesn't depend on a specific Drizzle transaction type. Pass it through to other Drizzle calls if you need to drop down to raw queries inside the transaction.

Mixing package methods with raw Drizzle

import { eq } from 'drizzle-orm';
import { posts, users } from './db/schema';

await users.executeSqlTransaction(async (tx) => {
  const user = await users.create({ name: 'Ada' }, { transaction: tx });
  // Raw Drizzle inside the same transaction:
  await tx.insert(posts).values({ authorId: user.id, title: 'Hello' });
});

The tx is the standard Drizzle transaction handle — anything you can do with db you can do with tx, and it'll be enlisted in the same transaction.

Nesting

executeSqlTransaction can be nested in code, but each call opens its own savepoint. If an inner callback throws, the outer transaction is unaffected. For most use cases, prefer a single top-level executeSqlTransaction per request.

Failure modes

FailureBehavior
The callback throwsThe transaction is rolled back. The error propagates to the caller.
A nested massCreate throws BulkOperationExceptionThe transaction is rolled back. The exception propagates.
The connection drops mid-transactionThe database rolls back. The package surfaces the underlying error.

When to use transactions

  • Cross-service atomicity — creating a User and a Profile in one atomic unit.
  • Read-modify-write consistency — read a row, decide based on its value, write back, all in one transaction.
  • Bulk operations that need external side effects in the same atomic unit — see Bulk operations.

When not to use transactions

  • Read-only queriesfindAll, count, findOne don't need a transaction.
  • Independent writes — if the operations can fail or succeed independently, separate transactions are usually better (smaller blast radius on failure).
  • Long-running operations — transactions hold locks. Don't include network calls, queue publishes, or unbounded awaits inside a transaction.

Next

On this page