Node/Express: Why can't you pass res to a helper function (e.g. for validation) and avoid ERR_HTTP_HEADERS_SENT?

Issue

I have a web app built with Node (v.18.2) and Express (v. 4.18). Users can send POST requests, which I validate when they arrive; if the user has made an error, I send back an error message so the user knows what went wrong.

In doing this, I wanted to add a number of helper functions, because these checks would happen in many endpoints and I wanted to keep my code DRY, e.g. has the user sent the correct type of input, does she have sufficient credits in her account, and so on.

My intention was to pass the res function to those requests to allow them to send back a response to the user. However, the code keeps running, hitting another res.json, which of course triggers the error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.

Here is a simplified example that I’ve distilled from my codebase:

app.post("/demonstration", async (req, res) => {
  const user = req.body.user;
  // user = {id: 123, credits: 0}

  // Validation - Check if user has enough credits
  await checkCredits(user, res);

  res.json({ result: "success" });
  return;
});

with the function defined as:

async function checkCredits(user, res) {
  const credits = user.credits;

  if (credits <= 0) {
    res.json({ result: "failure" });
    return;
  }
}

One immediate solution based on this post is to have checkCredits return true/false and then use this in an if/else statement the main body to decide whether to send a success or a failure response, as below (which works):

app.post("/demonstration", async (req, res) => {
  const user = req.body.user;
  // user = {id: 123, credits: 0}

  // Validation - Check if user has enough credits
  const hasEnoughCredits = await checkCredits(user); // Returns true if user has enough credits

  if (!hasEnoughCredits) {
    res.json({ result: "failure" });
    return;
  }

  res.json({ result: "success" });
  return;
});

I just don’t understand (A) why res can’t be passed around (in the first example) and/or (B) why res.json({}) and return are not enough to terminate the connection and stop attempting to send back additional responses.

I’d just like to understand what’s happening here before I move on, so if anyone has any tips or links to further reading (e.g. docs or SO posts I have missed when reading up on this), I would highly appreciate your insights.

Solution

(A) why res can’t be passed around (in the first example)

It can. And is.

(B) why res.json({}) and return are not enough to terminate the connection and stop attempting to send back additional responses.

return only stops the current function.

It wouldn’t be very useful if return stopped every function all the way up the call stack. const foo = bar(); would exist the function it was in before you could do anything with foo!

Answered By – Quentin

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published