Conversation
…equest logging Co-Authored-By: mpb <modulospb@protonmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| return err?.name === 'TimeoutError' || err?.name === 'AbortError'; | ||
| } | ||
|
|
||
| router.get('/', async (req, res) => { |
There was a problem hiding this comment.
🔴 Removal of outer try/catch + next(err) in Express 4 async handler causes unhandled promise rejections
The old code wrapped the entire route handler body in try { ... } catch (err) { return next(err); }, which is the standard pattern for safely handling errors in Express 4 async route handlers (Express 4 does not automatically catch rejected promises from async handlers). The new code removes this outer safety net and also removes the next parameter entirely. Any unexpected runtime error thrown outside the two specific try/catch blocks (lines 63-83 and 94-101) — such as in parseDrink at line 104 or res.json() at line 106 — will become an unhandled promise rejection instead of being routed to the errorHandler middleware (src/middleware/errorHandler.js).
A concrete scenario: if the upstream API returns { drinks: [null] }, drinks.map(parseDrink) will call parseDrink(null), which accesses null['strIngredient1'] at src/routes/cocktails.js:19, throwing a TypeError. With no outer catch, this crashes or leaves the request hanging.
Prompt for agents
The route handler at src/routes/cocktails.js:42 is an async function mounted on Express 4 (which does NOT auto-catch rejected promises from async handlers). The old code had try { ... } catch (err) { return next(err); } wrapping all logic, forwarding unexpected errors to the Express error handler middleware. The new code removed this pattern and also dropped the `next` parameter.
To fix: either (1) re-add the `next` parameter and wrap the handler body in a top-level try/catch that calls `next(err)`, or (2) use an async error-wrapping utility (e.g. express-async-errors or a custom wrapper). The specific try/catch blocks for fetch and upstream.json can remain, but an outer catch is needed for everything else — particularly lines 103-106 where parseDrink could throw on malformed upstream data (e.g. null elements in the drinks array).
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Hardens
GET /api/cocktailsso that any upstream failure is surfaced as a descriptive JSON error to the client instead of leaving the browser with a genericTypeError: Failed to fetch. Motivated by a report that letters other thanafailed withFailed to fetchin the UI — I could not reproduce locally (curl + Chromium both succeeded for every letter a–z against the deployed code), so this PR is defensive, not a confirmed root cause.Changes:
src/routes/cocktails.js):AbortSignal.timeout(FETCH_TIMEOUT_MS)— previously the request could hang forever, which browsers surface asFailed to fetch.User-Agent: api00/...andAccept: application/jsonheaders (some CDNs/firewalls reject requests with Node's default UA).TimeoutError/AbortError→ 504Upstream thecocktaildb timed out after 10000ms.Upstream thecocktaildb unreachable.+detail.upstream.json()throwing (HTML rate-limit page, truncated body, …) → 502…invalid JSON response.fetch(< 18), return 500 with a clear message instead of an opaque crash.console.errors a one-line diagnostic on the server so local users can paste it into a bug report.src/app.js): logsMETHOD URL -> STATUS MSon every finished response. Off whenNODE_ENV=test(keeps test output quiet);createApp({ logRequests })accepts an explicit override.tests/cocktails.test.js): 4 new cases covering the timeout (504), generic fetch rejection (502), invalid-JSON (502), and the UA/AbortSignal headers. Two existing assertions switched fromtoHaveBeenCalledWith(url)tomock.calls[0][0]sincefetchnow receives an options object. Cache-test letter changed to avoid collision with the new tests (the module-level cache is cleared inbeforeEach, but the letters are kept distinct for clarity).All 14 tests,
npm run lint, andnpm run format:checkpass locally.Review & Testing Checklist for Human
npm install,npm run dev, reload/, and try letters that previously failed. Expected: either (a) they work, or (b) the UI shows a descriptive error (timed out…,unreachable…, etc.) instead ofFailed to fetch. Share the server console line and the DevTools → Network response for the failing request so we can pinpoint the real root cause.node --versionis ≥ 20 (per.nvmrc).AbortSignal.timeoutis stable from Node 20; on older Node it will throw at request time. This PR adds a guard for missingfetchbut not for missingAbortSignal.timeout.FETCH_TIMEOUT_MSif needed.Notes
Failed to fetchlocally — curl and Chromium both succeeded for every letter a–z against the pre-fix code. If the bug persists after this PR, the most likely remaining causes are: (1) Node runtime missingAbortSignal.timeout, (2) the dev server crashing on their machine (the new request logger will make this obvious), or (3) thecocktaildb rate-limiting their IP after the first call (the new 502 +detailwill expose this).createApp()'s signature is still zero-arg compatible; the new{ logRequests }option has a sensible default.Link to Devin session: https://app.devin.ai/sessions/38829c1a13124d54a5fdf114ce348ba9
Requested by: @ModulosPB