Transactions
Execute multiple database operations atomically within functions. All operations within a transaction either succeed together or fail together.
Overview
Transactions allow you to:
- Execute multiple operations as a single atomic unit
- Read your own uncommitted changes within the transaction
- Rollback all changes if any operation fails
- Choose isolation levels for concurrent access control
Basic Usage
Use beginTransaction() to start a transaction, then use the returned handle for all operations:
const tx = await beginTransaction();
try {
// All operations use the transaction handle
const order = await tx.post('orders', {
customerId: 'c123',
total: 99.99
});
await tx.post('order-items', {
orderId: order.data[0],
productId: 'p456',
quantity: 2
});
// Commit persists all changes
await tx.commit();
} catch (e) {
// Rollback discards all changes
await tx.rollback();
throw e;
}
Transaction Handle Methods
The transaction handle provides the same HTTP methods as the global context:
| Method | Description |
|---|---|
tx.get(url) | GET request within transaction |
tx.post(url, data) | POST request within transaction |
tx.put(url, data) | PUT request within transaction |
tx.patch(url, data) | PATCH request within transaction |
tx.delete(url) | DELETE request within transaction |
tx.commit() | Persist all changes |
tx.rollback() | Discard all changes |
Reading Your Own Writes
Within a transaction, you can read data you've just written (even before commit):
const tx = await beginTransaction();
// Create a product
await tx.post('products', {
name: 'Widget',
price: 29.99,
stock: 100
});
// Read it back within the same transaction
const products = await tx.get('products?filter=name eq "Widget"');
log(products.data[0].stock); // 100
await tx.commit();
Error Handling
Always wrap transactions in try-catch to ensure proper cleanup:
const tx = await beginTransaction();
try {
await tx.post('accounts', {id: 'a1', balance: -100}); // Debit
await tx.patch('accounts/a2', {balance: 100}); // Credit
// Validate business rules
const account = await tx.get('accounts/a1');
if (account.data[0].balance < 0) {
throw new Error('Insufficient funds');
}
await tx.commit();
} catch (e) {
await tx.rollback();
logError('Transfer failed: ' + e.message);
throw e;
}
Use Cases
| Scenario | Description |
|---|---|
| Order processing | Create order and line items atomically |
| Inventory updates | Deduct stock and create order together |
| Account transfers | Debit one account and credit another |
| Bulk operations | Insert multiple related records |
| Data migrations | Update related records consistently |
Best Practices
- Keep transactions short — Long transactions can block other operations
- Always handle errors — Use try-catch and call
rollback()on failure - Don't mix with global methods — Use only
tx.*methods inside a transaction - Validate before commit — Check business rules within the transaction