The SurrealTransaction class provides transaction support for executing multiple queries atomically. When all desired queries have been executed, call commit() to apply the changes to the database, or cancel() to discard them.
Transactions are created using the beginTransaction() method on a SurrealSession instance.
Extends: SurrealQueryable
Source: api/transaction.ts
Overview
Transactions ensure that a group of operations are executed atomically - either all succeed or all fail. This is essential for maintaining data consistency when performing related operations.
const txn = await db.beginTransaction();
try {
// All operations succeed or fail together
await txn.create(recordId1).content(data1);
await txn.create(recordId2).content(data2);
await txn.commit(); // Apply all changes
} catch (error) {
await txn.cancel(); // Discard all changes
}Constructor
The constructor is not called directly. Use SurrealSession.beginTransaction() to create transactions.
Transaction methods
.commit()
Commit the transaction to the database, applying all changes made within the transaction scope.
After committing, the transaction cannot be used again.
txn.commit()Returns
`Promise` - Resolves when the transaction is committed
Example
const txn = await db.beginTransaction();
await txn.create(new RecordId('users', 'alice'))
.content({ name: 'Alice', email: 'alice@example.com' });
await txn.create(new RecordId('users', 'bob'))
.content({ name: 'Bob', email: 'bob@example.com' });
// Commit both creates atomically
await txn.commit();
console.log('Transaction committed successfully'); .cancel()
Cancel and discard all changes made in the transaction.
After canceling, the transaction cannot be used again.
txn.cancel()Returns
`Promise` - Resolves when the transaction is canceled
Example
const txn = await db.beginTransaction();
try {
await txn.create(new RecordId('users', 'alice'))
.content({ name: 'Alice' });
// Something goes wrong
throw new Error('Validation failed');
} catch (error) {
// Discard all changes
await txn.cancel();
console.log('Transaction cancelled:', error.message);
}Inherited methods
As SurrealTransaction extends SurrealQueryable, it inherits all query execution methods. All queries executed on a transaction instance are part of the transaction scope:
Query methods
query()- Execute raw SurrealQLselect()- Select recordscreate()- Create recordsinsert()- Insert recordsupdate()- Update recordsupsert()- Upsert recordsdelete()- Delete recordsrelate()- Create graph relationshipslive()- Subscribe to live queriesliveOf()- Subscribe to existing live queriesrun()- Execute functionsauth()- Get authenticated record userapi()- Access user-defined APIs
All of these operations are executed within the transaction context and will be committed or canceled together.
Complete examples
Basic transaction
import { Surreal, RecordId } from 'surrealdb';
const db = new Surreal();
await db.connect('ws://localhost:8000');
await db.use({ namespace: 'main', database: 'main' });
// Start a transaction
const txn = await db.beginTransaction();
try {
// Create a user
const user = await txn.create(new RecordId('users', 'john'))
.content({
name: 'John Doe',
email: 'john@example.com',
balance: 1000
});
// Create a purchase
const purchase = await txn.create(new RecordId('purchases', 'purchase1'))
.content({
user: new RecordId('users', 'john'),
amount: 100,
item: 'Widget'
});
// Update user balance
await txn.update(new RecordId('users', 'john'))
.merge({ balance: 900 });
// Commit all changes atomically
await txn.commit();
console.log('Purchase completed successfully');
} catch (error) {
// If anything fails, cancel the transaction
await txn.cancel();
console.error('Purchase failed:', error);
}Money transfer transaction
async function transferMoney(
db: Surreal,
fromUser: string,
toUser: string,
amount: number
) {
const txn = await db.beginTransaction();
try {
// Get current balances
const from = await txn.select(new RecordId('users', fromUser));
const to = await txn.select(new RecordId('users', toUser));
if (!from || !to) {
throw new Error('User not found');
}
if (from.balance < amount) {
throw new Error('Insufficient funds');
}
// Update both balances
await txn.update(new RecordId('users', fromUser))
.merge({ balance: from.balance - amount });
await txn.update(new RecordId('users', toUser))
.merge({ balance: to.balance + amount });
// Create transaction record
await txn.create(new RecordId('transactions', crypto.randomUUID()))
.content({
from: new RecordId('users', fromUser),
to: new RecordId('users', toUser),
amount,
timestamp: new Date()
});
// Commit all changes
await txn.commit();
console.log(`Transferred ${amount} from ${fromUser} to ${toUser}`);
return true;
} catch (error) {
await txn.cancel();
console.error('Transfer failed:', error);
return false;
}
}
// Use the function
await transferMoney(db, 'alice', 'bob', 50);Complex transaction with graph relationships
async function createUserWithFollows(
db: Surreal,
userData: { name: string; email: string },
followUserIds: string[]
) {
const txn = await db.beginTransaction();
try {
// Create the user
const userId = crypto.randomUUID();
const user = await txn.create(new RecordId('users', userId))
.content(userData);
// Create follow relationships
for (const followId of followUserIds) {
await txn.relate(
new RecordId('users', userId),
new Table('follows'),
new RecordId('users', followId),
{ followedAt: new Date() }
);
}
// Create initial activity log
await txn.create(new RecordId('activity', crypto.randomUUID()))
.content({
user: new RecordId('users', userId),
action: 'user_created',
timestamp: new Date()
});
// Commit everything
await txn.commit();
console.log('User and relationships created successfully');
return user;
} catch (error) {
await txn.cancel();
console.error('Failed to create user:', error);
throw error;
}
}Transaction with error handling
async function atomicBulkOperation(db: Surreal, records: any[]) {
const txn = await db.beginTransaction();
const results: any[] = [];
const errors: any[] = [];
try {
for (const record of records) {
try {
const result = await txn.create(new Table('items'))
.content(record);
results.push(result);
} catch (error) {
errors.push({ record, error });
}
}
// Only commit if all succeeded
if (errors.length === 0) {
await txn.commit();
console.log(`Successfully created ${results.length} records`);
return { success: true, results };
} else {
await txn.cancel();
console.log(`Failed with ${errors.length} errors, rolled back`);
return { success: false, errors };
}
} catch (error) {
await txn.cancel();
console.error('Transaction failed:', error);
return { success: false, errors: [error] };
}
}Best practices
1. Always handle errors
Always use try-catch blocks to ensure transactions are properly canceled on errors:
const txn = await db.beginTransaction();
try {
// Operations
await txn.commit();
} catch (error) {
await txn.cancel(); // Important!
throw error;
}2. Keep transactions short
Execute transactions quickly to avoid locking resources:
// Good: Short transaction
const txn = await db.beginTransaction();
await txn.update(recordId).merge(data);
await txn.commit();
// Avoid: Long-running operations in transactions
const txn = await db.beginTransaction();
await expensiveExternalApiCall(); // Bad!
await txn.update(recordId).merge(data);
await txn.commit();3. Don't reuse transactions
Once a transaction is committed or canceled, create a new one for subsequent operations:
const txn1 = await db.beginTransaction();
await txn1.create(record1);
await txn1.commit();
// Create a new transaction for next operation
const txn2 = await db.beginTransaction();
await txn2.create(record2);
await txn2.commit();4. Validate before transaction
Perform validation before starting a transaction when possible:
// Validate first
if (!isValidEmail(email)) {
throw new Error('Invalid email');
}
// Then transact
const txn = await db.beginTransaction();
// ... operations
await txn.commit();See also
SurrealSession.beginTransaction() - Creating transactions
SurrealQueryable - Available query methods
Query builders - Query builder classes