Home > front end >  Node/Express: Why can't you pass res to a helper function (e.g. for validation) and avoid ERR_H
Node/Express: Why can't you pass res to a helper function (e.g. for validation) and avoid ERR_H

Time:08-15

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.

CodePudding user response:

Have your helper functions return false after reporting the error to the user (true if there was no error) and check the return value when you invoke them:

async function checkCredits(user, res) {
  const credits = user.credits;
  if (credits <= 0) {
    res.json({ result: "failure" });
    return false;
  }
  return true;
}
app.post("/demonstration", async (req, res) => {
  const user = req.body.user;
  if (!(await checkCredits(user, res))) return;
  res.json({ result: "success" });
  return;
});

A return in one function terminates only that function, it does not terminate the request processing.

CodePudding user response:

(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!

  • Related