Building JSON with Node.js with multiple queries
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.
Introduction
A common backend task is fetching data from multiple database tables and combining the results into a single JSON response. In Node.js, doing this correctly means understanding asynchronous control flow, because each database query is a non-blocking operation. Getting this wrong leads to callback hell, race conditions, or incomplete responses sent before all queries finish.
The Callback Hell Problem
Before Promises became standard, Node.js developers nested callbacks to run sequential queries. This approach works but quickly becomes unreadable and error-prone:
Each level of nesting adds indentation and makes error handling repetitive. Worse, these queries run sequentially even though they do not depend on each other, wasting time.
Using Promise.all with async/await
The modern approach wraps each query in a Promise and runs independent queries in parallel with Promise.all. This is both faster and more readable:
All three queries start at the same time. The total wait time equals the slowest query rather than the sum of all three. The destructured result array keeps the code flat and easy to follow.
Combining Query Results Into a Response Object
When building complex JSON responses, you often need to reshape raw query results. Keep the transformation logic separate from the querying logic:
This separation makes the response structure easy to test independently from the database layer.
Error Handling for Partial Failures
Promise.all rejects as soon as any single promise rejects, which means one failed query aborts the entire response. If some data is optional and you want to return partial results, use Promise.allSettled instead:
This way, the user and orders load even if the recommendations query fails. The warnings array tells the client which parts are missing.
Using Transactions for Dependent Queries
When queries depend on each other, or when you need read-consistency across multiple tables, wrap them in a database transaction:
Transactions ensure you get a consistent snapshot. Without them, a payment could be recorded between the orders query and the balance query, producing inconsistent data.
Practical Example With Express and mysql2
Here is a complete working example using Express and the mysql2/promise driver:
Common Pitfalls
- Running independent queries sequentially with await: If queries do not depend on each other, always use
Promise.allto run them in parallel. Sequential awaits waste time equal to the sum of all query durations. - Forgetting to release database connections: When using connection pools with manual
getConnection(), always callconnection.release()in afinallyblock. Leaked connections exhaust the pool and freeze your server. - Sending the response before all queries complete: Without
awaitor proper callback coordination,res.json()can execute before query results are available, sendingundefinedor empty data. - Not handling null or empty results: A query might return zero rows. Always check for empty arrays before accessing index 0, or the response will contain
undefinedfields. - Exposing raw database errors to clients: Catch errors and return a generic message. Sending the raw error object can leak table names, column names, and query details to attackers.
Summary
- Use
Promise.allwithasync/awaitto run independent database queries in parallel and combine results into a single JSON response. - Separate data fetching from response shaping to keep code testable and maintainable.
- Use
Promise.allSettledwhen some queries are optional and partial responses are acceptable. - Wrap dependent or consistency-critical queries in a database transaction.
- Always handle errors gracefully, release connections in
finallyblocks, and never expose raw database errors to clients.
Related reading
- Button background as transparent
- Calculate Standard Deviation in TensorflowJS?
- Calculating Standard Deviation of Angles?
- Call An Asynchronous Javascript Function Synchronously
- Call async/await functions in parallel
- Call async/await functions in parallel
- Call multiple async methods that rely on each other
- Callback after all asynchronous forEach callbacks are completed
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
Interview Questions practice on Codemia
Over 8,000 real interview questions from top companies, searchable by company and role.