Why am I getting “Cannot set headers after they are sent to the client” error in Nodejs?

I am getting Cannot set headers after they are sent to the client error in nodejs and I cant figure out why.the code is given below:I am using mongoose to save data in mongodb.I am trying to save the data and check for a specific condition and return accordingly but the error keeps persisting.

const trendingposts = (req, res) => {
  Post.find({}, function (err, docs) {
    if (docs.length == 0) 
       return res.send({message:"No posts"});
    
    docs.forEach(function (data) {
      var id = data.id;
      var likes = data.likes.length;

      var comments = data.comments.length;
      const date2 = new Date();
      const diffTime = Math.abs(date2 - data.created);
      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
      Post.findByIdAndUpdate(
        id,
        { $inc: { score: (likes * comments) / diffDays } },
        function (errr, doc) {
          if (errr) {
            console.log(err);
          }
        }
      );
    });
  }).exec((err, posts) => {
    if (err) {
      console.log(err);
    }
  });
  var mysort = { score: -1 };
  Post.find({})
    .populate("postedBy")
    .populate("comments.postedBy")
    .populate("comments.incomments.postedBy")
    .populate("comments.likes")
    .sort(mysort)
    .limit(10)
    .exec((er, result) => {
      
      res.json(result);
    });
};

Answers 1

  • Right here:

    Post.find({}, function(err, docs) {
        if (docs.length == 0)
            return res.send({ message: "No posts" });
    

    If you hit that condition of docs.length == 0, then you will send a response to the request. But, your return is ONLY returning from the Post.find() callback. It's not returning from your trendingposts() function.

    So, meanwhile, that function continues to execute and eventually gets to this code:

    var mysort = { score: -1 };
    Post.find({})
        .populate("postedBy")
        .populate("comments.postedBy")
        .populate("comments.incomments.postedBy")
        .populate("comments.likes")
        .sort(mysort)
        .limit(10)
        .exec((er, result) => {
    
            res.json(result);
        });
    

    Where you then send another response to the same request. That's what triggers the error Cannot set headers after they are sent to the client that you see.


    There are many different ways to prevent this, but they are probably all related to how you would generally clean up this function. The way it is written now, you essentially start two completely separate asynchronous code paths. Both start with Post.find({}) and go from there. They each run in parallel and neither has any idea what the other code path is doing. As such, you have no concrete way to send a response from one, but not both.

    So, the way to clean this up is probably to not have two completely separate asynchronous code paths. You need to coordinate them in some way. In pretty much all cases here, you will want to switch over to the promise-interface to your database as that will give you a lot more options for managing your control flow. For example, if for performance reasons, you want to have two parallel asynchronous operations going at once, with promises, you can use Promise.all() or Promise.allSettled() to monitor both and know when they are done and then, with both results in hand, decide which response to send.

    Or, if you want to sequence them, you can use async/await to fairly easily sequence the two operations and then when you do a return, it will actually return from the top-level function and will stop further control flow.

    If you want to stick with the callback interface to your database, then you will probably have to nest the second operation into the first option so that you don't start the second operation if you're going to do res.send({ message: "No posts" }).


Related Articles