A transaction groups several writes so they all succeed or all fail together. If any step throws, the database undoes everything as if none of it happened. Think of a bank transfer: money leaves one account and arrives in another. You never want one half to happen without the other.
How to use it
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
// $transaction hands you a scoped `tx` client; everything on it commits together
await prisma.$transaction(async (tx) => {
await tx.account.update({
where: { id: fromAccount },
data: { balance: { decrement: 100 } },
});
await tx.account.update({
where: { id: toAccount },
data: { balance: { increment: 100 } },
});
});
// If the second update throws, the first is rolled back automatically. Common reasons you may need one
- Any operation that writes to more than one row or table and must agree
- Moving a value from one place to another (transfers, inventory, balances)
- Steps where a partial result would leave your data in a broken state
If you're only doing a single write, you don't need an explicit transaction. One statement is already all-or-nothing.
Exercise
Node challenge · runs in your browser
Make the transfer atomic
Move amount from one account to another so the two updates commit
together or not at all. Right now the starter runs two bare updates: a transfer to a
missing account leaves the source account already debited.
Wrap both updates in prisma.$transaction so a failure rolls back the
first write. A mock Prisma client in prisma.js snapshots balances and
restores them when the transaction throws.