Q71: How do you handle complex transaction rollbacks when a Server Action fails midway through execution?

Because Server Actions execute arbitrary Node.js code, multi-step mutations (e.g., charge credit card -> update database -> send email) can fail halfway through. Complex Server Actions must wrap mutations inside atomic database transactions (db.$transaction()) and implement explicit compensation logic (e.g. issuing refund calls if email delivery fails) to prevent database corruption.

Atomic Database Transaction in Server Action
'use server';
import db from '@/lib/db';

export async function processOrderAction(orderData) {
  return await db.$transaction(async (tx) => {
    // 1. Deduct account balance
    const user = await tx.user.update({ where: { id: orderData.userId }, data: { balance: { decrement: orderData.amount } } });
    if (user.balance < 0) throw new Error('Insufficient funds'); // Rolls back transaction!
    
    // 2. Create order record
    return await tx.order.create({ data: orderData });
  });
}
💡 Using database transactions guarantees that if account balance drops below zero, all writes roll back automatically.
Key Takeaways
  • Wrap multi-table database writes inside atomic transactions (db.$transaction).
  • Structure actions to fail safely before executing irreversible external API side-effects.
Knowledge Check

Why should multi-step database writes inside Server Actions be wrapped within explicit database transactions?