SurrealTransaction
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 {
await txn.create(recordId1).content(data1);
await txn.create(recordId2).content(data2);
await txn.commit();
} catch (error) {
await txn.cancel();
}
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.
Method Syntax
txn.commit()
Returns
Promise<void> - 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' });
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.
Method Syntax
txn.cancel()
Returns
Promise<void> - Resolves when the transaction is canceled
Example
const txn = await db.beginTransaction();
try {
await txn.create(new RecordId('users', 'alice'))
.content({ name: 'Alice' });
throw new Error('Validation failed');
} catch (error) {
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
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: 'test', database: 'test' });
const txn = await db.beginTransaction();
try {
const user = await txn.create(new RecordId('users', 'john'))
.content({
name: 'John Doe',
email: 'john@example.com',
balance: 1000
});
const purchase = await txn.create(new RecordId('purchases', 'purchase1'))
.content({
user: new RecordId('users', 'john'),
amount: 100,
item: 'Widget'
});
await txn.update(new RecordId('users', 'john'))
.merge({ balance: 900 });
await txn.commit();
console.log('Purchase completed successfully');
} catch (error) {
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 {
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');
}
await txn.update(new RecordId('users', fromUser))
.merge({ balance: from.balance - amount });
await txn.update(new RecordId('users', toUser))
.merge({ balance: to.balance + amount });
await txn.create(new RecordId('transactions', crypto.randomUUID()))
.content({
from: new RecordId('users', fromUser),
to: new RecordId('users', toUser),
amount,
timestamp: new Date()
});
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;
}
}
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 {
const userId = crypto.randomUUID();
const user = await txn.create(new RecordId('users', userId))
.content(userData);
for (const followId of followUserIds) {
await txn.relate(
new RecordId('users', userId),
new Table('follows'),
new RecordId('users', followId),
{ followedAt: new Date() }
);
}
await txn.create(new RecordId('activity', crypto.randomUUID()))
.content({
user: new RecordId('users', userId),
action: 'user_created',
timestamp: new Date()
});
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 });
}
}
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 {
await txn.commit();
} catch (error) {
await txn.cancel();
throw error;
}
2. Keep Transactions Short
Execute transactions quickly to avoid locking resources:
const txn = await db.beginTransaction();
await txn.update(recordId).merge(data);
await txn.commit();
const txn = await db.beginTransaction();
await expensiveExternalApiCall();
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();
const txn2 = await db.beginTransaction();
await txn2.create(record2);
await txn2.commit();
4. Validate Before Transaction
Perform validation before starting a transaction when possible:
if (!isValidEmail(email)) {
throw new Error('Invalid email');
}
const txn = await db.beginTransaction();
await txn.commit();
See Also