Async Query Patterns
Database queries in Node.js are asynchronous—they return Promises (or accept callbacks in older drivers). Use async/await for sequential queries and Promise.all for parallel independent queries.
Sequential awaits = ordering food, waiting for it to arrive, THEN ordering drinks. Promise.all = ordering food AND drinks at the same time—both arrive in parallel. Transactions = one waiter (connection) who must carry ALL your dishes on one tray (BEGIN...COMMIT) or drop them all (ROLLBACK).
Sequential (dependent) queries use await: `const user = await getUser(id); const orders = await getOrders(user.id)`. Parallel (independent) queries use Promise.all: `const [user, products] = await Promise.all([getUser(id), getProducts()])`. Promise.allSettled() runs all queries regardless of failures, returning status+value/reason for each. Transactions require a single connection held across multiple queries: acquire a client from the pool, BEGIN, run queries, COMMIT or ROLLBACK on error, release the client.
Transaction isolation in Node.js requires careful connection management. Using pool.query() for transactions is wrong—pool.query() may use different connections for each call. Correct pattern: `const client = await pool.connect(); await client.query('BEGIN'); try { await client.query(q1); await client.query(q2); await client.query('COMMIT'); } catch (e) { await client.query('ROLLBACK'); throw e; } finally { client.release(); }`. Async iterators for large result sets: pg's Cursor and many ORMs support streaming query results, enabling per-row processing without buffering the entire result set in memory. For high-throughput bulk inserts, use multi-value INSERT statements or COPY (PostgreSQL) rather than individual INSERT per row.
Use async/await for sequential queries, Promise.all for parallel independent queries. Database transactions require a single borrowed pool connection across all query calls—using pool.query() for transactions is a bug because different calls may use different connections. Always release connections in a finally block. Stream large result sets with database cursors to avoid buffering entire tables in memory.
await query1; await query2 when the queries are independent serializes them unnecessarily. Two 50ms queries run sequentially take 100ms; with Promise.all they take 50ms. In a high-traffic API this serialization compounds across thousands of requests. Profile query execution to identify sequential waits on independent operations and parallelize them.