Atomic multi-step updates
Move funds between accounts while ensuring debit and credit succeed or fail together.
Drizzle ORM · Module
db.transaction runs related SQL operations atomically: all changes commit together, or the transaction rolls back. Use the supplied tx handle for queries, nested callbacks for savepoints, and tx.rollback() to abort from business logic.
Call db.transaction with an async callback to execute related statements through the transaction-scoped tx object. The callback may return a value; throwing an exception or calling tx.rollback() cancels the transaction, while nested transaction calls provide savepoints.
await db.transaction(async (tx) => { /* statements using tx */ });| Name | Type | Description |
|---|---|---|
fn | (tx) => Promise<T> | Asynchronous callback receiving the transaction-scoped query handle. |
config | transaction configuration | Optional dialect-specific transaction configuration, such as isolation level, access mode, or deferrability. |
The value returned by the transaction callback, resolved as a promise.
tx.rollback() aborts the current transaction by throwing, so later statements in the callback do not run.import { drizzle } from "drizzle-orm/node-postgres";
import { eq, sql } from "drizzle-orm";
import { accounts, users } from "./schema";
const db = drizzle(process.env.DATABASE_URL!);
await db.transaction(async (tx) => {
const [account] = await tx
.select({ balance: accounts.balance })
.from(accounts)
.where(eq(users.name, "Dan"));
if (account.balance < 100) {
tx.rollback();
}
await tx.update(accounts)
.set({ balance: sql`${accounts.balance} - 100` })
.where(eq(users.name, "Dan"));
await tx.update(accounts)
.set({ balance: sql`${accounts.balance} + 100` })
.where(eq(users.name, "Andrew"));
});Runs both balance updates as one atomic unit and explicitly rolls back when the source account lacks funds.
Move funds between accounts while ensuring debit and credit succeed or fail together.
Check application conditions and cancel before performing writes.
Group a smaller operation inside a larger unit with savepoint semantics.
db.transaction provide?A transaction groups SQL statements so they commit as one logical unit or roll back as one. Drizzle starts it with db.transaction(async (tx) => { ... }), and statements should use tx.
Follow-up: How can application logic abort it? Call tx.rollback() or let an exception escape; both prevent the transaction from committing.
Use tx.transaction(async (tx2) => { ... }) inside an outer transaction. Drizzle maps nested transactions to savepoints, allowing the nested work to be independently rolled back.
Follow-up: What is the practical benefit? A failed nested operation can return to its savepoint while preserving the surrounding transaction, subject to the database dialect's behavior.
The transaction callback can return a value, which becomes the resolved result of the transaction call. This lets a transaction perform writes and return selected data without a separate query.
Follow-up: Can relational queries run inside it? Yes; the transaction-scoped object can be used for relational queries as documented.
savepointsnested transactionsrelational queriesisolationLevelaccessModedeferrabletx.rollback