SurrealDB
SurrealDB Docs Logo

Enter a search query

Navigation

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 { // 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.

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' }); // 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.

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' }); // 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

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' }); // 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