Application Transactions
A transaction involving multiple statements generally requires a single checked-out connection so all statements execute in the same transaction. Calling pool.query separately for each statement does not guarantee that the same connection is used. Acquire a client, issue BEGIN, perform statements, COMMIT on success, ROLLBACK on failure, and release the client in a finally block.
Avoid making network calls to unrelated services while holding a database transaction open unless the design explicitly requires it. Long transactions hold resources and can worsen contention. For distributed workflows, consider idempotency and patterns such as an outbox rather than assuming a database transaction can atomically commit a remote API request.
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query(
"UPDATE inventory SET quantity = quantity - $1 WHERE product_id = $2",
[quantity, productId]
);
await client.query("COMMIT");
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}- Use one checked-out client for a multi-statement transaction.
- Rollback on failure and always release the client.
- Keep transactions short and avoid treating remote calls as part of a local DB transaction.
Why should a transaction use the same checked-out client for all statements?