Transaction handling helps keep your data safe by making sure a group of actions all succeed or all fail together.
Transaction handling in Node.js
async function doTransaction() { const client = await pool.connect(); try { await client.query('BEGIN'); // your queries here await client.query('COMMIT'); } catch (e) { await client.query('ROLLBACK'); throw e; } finally { client.release(); } }
Use BEGIN to start a transaction, COMMIT to save changes, and ROLLBACK to undo if something goes wrong.
Always release the database client after finishing to avoid connection leaks.
await client.query('BEGIN'); await client.query('INSERT INTO users(name) VALUES($1)', ['Alice']); await client.query('COMMIT');
try { await client.query('BEGIN'); await client.query('UPDATE accounts SET balance = balance - 100 WHERE id = $1', [fromId]); await client.query('UPDATE accounts SET balance = balance + 100 WHERE id = $1', [toId]); await client.query('COMMIT'); } catch (e) { await client.query('ROLLBACK'); throw e; }
This program safely transfers money between two accounts. It checks the balance, updates both accounts, and uses a transaction to keep data correct.
import pkg from 'pg'; const { Pool } = pkg; const pool = new Pool({ user: 'user', host: 'localhost', database: 'testdb', password: 'password', port: 5432, }); async function transferMoney(fromId, toId, amount) { const client = await pool.connect(); try { await client.query('BEGIN'); const res1 = await client.query('SELECT balance FROM accounts WHERE id = $1', [fromId]); if (res1.rows.length === 0) throw new Error('From account not found'); if (res1.rows[0].balance < amount) throw new Error('Insufficient funds'); await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, fromId]); await client.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, toId]); await client.query('COMMIT'); console.log('Transfer completed'); } catch (e) { await client.query('ROLLBACK'); console.error('Transfer failed:', e.message); } finally { client.release(); } } // Example usage transferMoney(1, 2, 50);
Always handle errors to rollback transactions and avoid corrupt data.
Transactions can lock rows, so keep them short to avoid slowing down other users.
Not all databases support transactions the same way; check your database docs.
Transactions group multiple database actions to succeed or fail together.
Use BEGIN, COMMIT, and ROLLBACK commands to control transactions.
Always release database connections and handle errors carefully.
Practice
Solution
Step 1: Understand the role of transactions
Transactions group several database actions so they either all succeed or all fail, maintaining data integrity.Step 2: Compare options
Only To ensure multiple database actions succeed or fail together correctly describes this purpose. Other options describe unrelated features.Final Answer:
To ensure multiple database actions succeed or fail together -> Option AQuick Check:
Transaction purpose = group actions for success/failure [OK]
- Thinking transactions speed up queries
- Confusing transactions with connection pooling
- Assuming transactions auto-log errors
Solution
Step 1: Recall standard SQL transaction commands
The standard command to start a transaction is 'BEGIN'.Step 2: Validate options
Only client.query('BEGIN') uses the correct SQL command 'BEGIN'. Other options are invalid SQL commands.Final Answer:
client.query('BEGIN') -> Option CQuick Check:
Start transaction command = BEGIN [OK]
- Using non-standard commands like 'START TRANSACTION'
- Confusing transaction start with connection open
- Using commands that don't exist in SQL
await client.query('BEGIN');
await client.query('INSERT INTO users(name) VALUES($1)', ['Alice']);
await client.query('COMMIT');
console.log('Transaction complete');
What will be printed if all queries succeed?Solution
Step 1: Analyze the transaction flow
The code starts a transaction, inserts a user, then commits the transaction.Step 2: Check output after successful commit
Since all queries succeed, the commit happens and the console logs 'Transaction complete'.Final Answer:
Transaction complete -> Option AQuick Check:
Successful commit logs message [OK]
- Assuming errors print without exceptions
- Confusing commit with rollback output
- Ignoring async/await behavior
try {
await client.query('BEGIN');
await client.query('INSERT INTO orders(id) VALUES(1)');
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
console.error('Transaction failed');
}Solution
Step 1: Review transaction error handling
The code correctly uses try-catch to rollback on error.Step 2: Identify missing error handling for rollback
If the ROLLBACK query itself fails, this code does not catch that error, which can cause unhandled exceptions.Final Answer:
No error handling for ROLLBACK failure -> Option BQuick Check:
Rollback errors need separate handling [OK]
- Assuming ROLLBACK never fails
- Placing BEGIN after queries
- Putting COMMIT inside catch block
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query('INSERT INTO accounts(id) VALUES(1)');
await client.query('INSERT INTO profiles(account_id) VALUES(1)');
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}Solution
Step 1: Check transaction flow and error handling
The code starts a transaction, performs two inserts, commits if successful, rolls back on error, and rethrows the error.Step 2: Verify connection release
The client connection is released in the finally block, ensuring it always happens regardless of success or failure.Final Answer:
This pattern is correct and safely handles transactions and connection release -> Option DQuick Check:
Try-catch-finally with commit, rollback, release = safe pattern [OK]
- Releasing connection too early
- Placing rollback in finally block
- Not rethrowing errors after rollback
Ready to prove it?
Pass the test and this pattern moves unknown
