Donkey

Find & Fix the Bug in Your Code

Paste your broken code and the error, and get the root cause, a corrected version, and a plain-English explanation of what went wrong.

What you get: A Markdown debugging report with: a one-line diagnosis, the root cause explained, the corrected code (full fixed snippet plus a focused diff), why the fix works, any other bugs or risks spotted, steps to verify the fix, and a suggested test to keep the bug from coming back. Price: $19 flat. Time: usually ready in minutes. Done by AI; you review it, with one free change.

We're not taking orders yet. Join the waitlist and we'll email you once when we open.

Real example

This is an actual result our AI made in a test run, from sample details. Yours is made from your details.

Show the example

Bug Fix Report

Diagnosis: Array.prototype.forEach doesn't wait for async callbacks, so res.json(results) runs before any db.getItems call has finished. The response is sent while results is still empty.

Confidence: High. The symptom matches exactly: you get [] with no error, your logs show items arriving after the response, and it started when the for loop became forEach.

What's going wrong

The problem is in this block of the /api/orders/:userId handler:

orders.forEach(async (order) => {
  const items = await db.getItems(order.id);
  results.push({ ...order, items });
});
res.json(results);

Here's what happens when it runs:

  1. forEach calls your async callback once per order. Each call runs until it reaches await db.getItems(...), then returns a pending Promise.
  2. forEach ignores what its callback returns, so it throws those Promises away. It finishes right away and never waits.
  3. The next line, res.json(results), runs at once. No getItems query has resolved yet and nothing has been pushed, so results is [] and that's what gets sent.
  4. Later, the queries finish and results.push(...) runs. The response is already gone, which is why your logs show items arriving "after the response is sent."

Your old for loop worked because await inside a plain for/for...of loop pauses the whole handler. Inside a forEach callback, it only pauses that one callback.

There's a second problem hidden in the same code. If any db.getItems call throws, the error happens inside a Promise nobody is watching, so your try/catch never sees it. Since Node 15, an unhandled promise rejection crashes the process by default, so one failed items query could take the whole server down.

The fix

Use map to turn each order into a Promise, then await Promise.all(...) so the handler waits for all of them. It's about as short as the forEach version, it keeps the original order of orders, and errors go into your existing catch.

const express = require('express');
const app = express();
const db = require('./db');

app.get('/api/orders/:userId', async (req, res) => {
  try {
    const orders = await db.getOrders(req.params.userId);
    const results = await Promise.all(
      orders.map(async (order) => {
        const items = await db.getItems(order.id);
        return { ...order, items };
      })
    );
    res.json(results);
  } catch (err) {
    console.error(err);
    res.status(500).json({ error: 'Server error' });
  }
});

app.listen(3000);
     const orders = await db.getOrders(req.params.userId);
-    const results = [];
-    orders.forEach(async (order) => {
-      const items = await db.getItems(order.id);
-      results.push({ ...order, items });
-    });
+    const results = await Promise.all(
+      orders.map(async (order) => {
+        const items = await db.getItems(order.id);
+        return { ...order, items };
+      })
+    );
     res.json(results);

Alternative (one query at a time, like your old loop): if you'd rather not run the queries in parallel, this also works:

const results = [];
for (const order of orders) {
  const items = await db.getItems(order.id);
  results.push({ ...order, items });
}

Why this works

  • orders.map(async ...) returns an array of Promises, one per order, so nothing gets thrown away the way it does with forEach.
  • await Promise.all(...) pauses the handler until every getItems call has finished. res.json only runs once results is filled in.
  • Promise.all returns results in the same order as the input array, so the orders stay in the order getOrders returned them. With the push version they'd come out in whatever order the queries happened to finish.
  • If any getItems call fails, Promise.all rejects and your catch sends a 500. Before, that error would have been an unhandled rejection.
  • With under 50 orders, running the queries in parallel is fine. If db uses a pg Pool, the pool limits how many queries run at once and queues the rest. If it uses a single pg Client, that client runs its queries one after another.

Other issues spotted

Issue Where Severity Suggested fix
No authorization check: anyone can call /api/orders/<anyId> and read another user's orders Route handler High (if this is exposed publicly) Check that the logged-in user matches req.params.userId (or is an admin) before querying. Return 403 if not.
Possible SQL injection, depending on how ./db builds its queries (not shown) db.getOrders / db.getItems High if they join strings, None if they use $1 parameters Make sure both use parameterized queries, e.g. pool.query('SELECT ... WHERE user_id = $1', [userId]). Never paste userId into the SQL string.
N+1 queries: one query for orders, then one per order for items Route handler Low (under 50 orders) Optional: fetch all items in one query with WHERE order_id = ANY($1) and group them by order_id in JS.
userId isn't validated req.params.userId Low If IDs are integers, reject anything that isn't a number with a 400, so bad input doesn't cause a database error and a 500.

How to verify the fix

  • Restart the server and call GET http://localhost:3000/api/orders/<userIdWithOrders>. You should get an array of orders, each with an items array.
  • Check that the orders come back in the same order db.getOrders returns them.
  • Keep your log inside the callback. Each order's log should now print before the response goes out.
  • Edge case: call it for a user with no orders. You should get [] with status 200, and it should be the correct answer this time.
  • Edge case: call it for an order with no line items. That order should come back with items: [], assuming getItems returns rows from pg.
  • Error path: make db.getItems throw temporarily, e.g. `thr

Questions

How does the find & fix the bug in your code work?

You answer a few questions, pay $19, and an AI worker does the job. The result appears on your private page, usually within minutes.

Can I get changes?

Yes. Every job includes one free change request: tell the donkey what to fix and it redoes the job with your notes.

Is it really done by AI?

Yes, fully. No human writes it. AI can make mistakes, so please review the result before you rely on it.

What happens to my information?

It is used only to do your job, is never used to train AI models, and is deleted after 30 days.

What if it fails?

Failed jobs are retried once, then refunded automatically.

Need something different?

Can't find it? Describe anything.

Our AI checks it's something we can do safely, then shows you exactly what you'll get and a flat price. Nothing is charged until you order.

More in Tech & code