JCC Express

Database

Transactions

Introduction

Transactions group multiple database writes into a single atomic unit: either all changes commit, or all are rolled back.

JCC Express MVC supports transactions through:

  • DB.transaction(async () => { ... })
  • query builder transaction support (builder-level wrapper)

Basic transaction usage

TypeScript
import { DB } from "jcc-express-mvc/Eloquent";

await DB.transaction(async () => {
  await DB.table("wallets").where("id", 1).decrement("balance", 100);
  await DB.table("wallets").where("id", 2).increment("balance", 100);
});

If an exception is thrown in the callback, the transaction is rolled back.


Error handling pattern

TypeScript
try {
  await DB.transaction(async () => {
    await DB.table("orders").insert(orderData);
    await DB.table("order_items").insert(items);
  });
} catch (error) {
  // transaction was rolled back
  throw error;
}

Keep business validation and side effects (emails/webhooks) thoughtfully ordered around transaction boundaries.


Query builder transaction wrapper

The builder also exposes transaction(...) for advanced chaining scenarios:

TypeScript
await DB.table("users").transaction(async () => {
  await DB.table("profiles").insert(profileData);
});

Use DB.transaction(...) as the default for readability unless you specifically need builder-level control.


Transaction-safe practices

  • Keep transaction blocks short.
  • Avoid network calls inside the transaction callback.
  • Prefer deterministic write order to reduce deadlock risk.
  • Use row-level locks/ordering patterns if your workload is highly concurrent.

Caveats

  • Avoid schema-altering statements inside app-level business transactions.
  • Raw unprepared SQL with user input is unsafe; always bind parameters.

Summary

  • Use DB.transaction(...) for atomic multi-step writes.
  • Throwing inside the callback rolls back all changes.
  • Keep transactions focused, short, and database-only.