Express.js Troubleshooting Guide
Issue 1: A request hangs forever — no response, no error
Symptom: A client request to a specific route never resolves — no response, no error in the server console, and eventually a client-side timeout (if one is set at all).
Root Cause: A middleware in the chain for that route neither called next() nor sent a response on the code path that actually executed. Express has no default timeout for this — it just waits.
Debug steps:
Watch which log lines print and which don't. The last one that prints, but whose middleware never logs anything after it (and never sends a response), is the culprit.
Fix patterns:
Issue 2: "Cannot set headers after they are sent to the client"
Symptom: A crash with the error Error: Cannot set headers after they are sent to the client, usually on a route that otherwise seems to work.
Root Cause: res.send()/res.json()/res.end() was called more than once for the same request — commonly because a middleware calls next() and also sends a response, or an async handler sends a response but then a later .catch()/error path tries to send another.
Debug steps:
Fix patterns:
Always return immediately after any res.send()/res.json() call inside a conditional — a missing return is the actual root cause almost every time this error appears.
Issue 3: An async route handler's thrown/rejected error crashes the process instead of being caught
Symptom: An error thrown (or a Promise rejected) inside an async route handler either crashes the entire Node process, or leaves the request hanging — instead of returning a clean error response.
Root Cause: Express's default synchronous error handling does not automatically catch a rejected Promise from an async handler the same way it catches a synchronous throw in Express 4.x. The rejection goes unhandled unless explicitly forwarded.
Debug steps:
Fix patterns:
(needs verification — recheck the exact current default behavior against the Express major version actually in use, since this specific mechanic has evolved across versions.)
Issue 4: CORS errors when calling the API from a frontend
Symptom: The browser console shows an error like has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present, even though the API itself returns a valid response when hit directly (via curl or Postman).
Root Cause: Browsers enforce the same-origin policy by default — a frontend on a different origin (different domain, port, or protocol) can't read the response unless the server explicitly opts in via CORS headers. curl/Postman aren't browsers, so they never hit this restriction, which is why the API can look "fine" while a browser client is blocked.
Debug steps:
Fix patterns:
Register cors() before the routes it needs to apply to, same as any other global middleware.
Issue 5: Request body is `undefined`
Symptom: req.body is undefined on a POST/PUT route, even though the client is clearly sending a JSON body with the correct Content-Type header.
Root Cause: express.json() middleware was never registered, or was registered after the route that needs it. Express does not parse request bodies automatically — it's entirely opt-in.
Debug steps:
Fix patterns:
Prevention Tips
return immediately after any res.send()/res.json()/res.end() call inside a conditionalexpress.json(), helmet, cors, logging) at the very top of app.js, before any routesasync route handler in try/catch + next(err), or use a wrapper utility — treat it as mandatory, not optionalnext() before anything else
