Some operations touch multiple rows and must succeed or fail as one unit. That's a transaction.
Transferring credits from one account to another is two writes:
1. Subtract 100 from account A
2. Add 100 to account B
If the server crashes between step 1 and 2, money vanishes. A transaction guarantees both happen or neither does.
1await prisma.$transaction([
2 prisma.account.update({ where: { id: a }, data: { balance: { decrement: 100 } } }),
3 prisma.account.update({ where: { id: b }, data: { balance: { increment: 100 } } }),
4]);When later steps depend on earlier ones, use the callback form:
1await prisma.$transaction(async (tx) => {
2 const order = await tx.order.create({ data: { userId, total } });
3 for (const item of items) {
4 await tx.orderItem.create({ data: { orderId: order.id, ...item } });
5 await tx.product.update({
6 where: { id: item.productId },
7 data: { stock: { decrement: item.qty } },
8 });
9 }
10});If any step throws, the whole thing rolls back — no half-created orders, no negative stock.
| Letter | Guarantee |
|---|---|
| Atomicity | All steps happen, or none do |
| Consistency | Constraints hold before and after |
| Isolation | Concurrent transactions don't see each other's half-done work |
| Durability | Once committed, it survives a crash |
A transaction holds locks. Don't call external APIs, send emails, or do slow work inside one — do that after it commits. Long transactions block other writers and cause deadlocks.