Search code examples
javascriptnode.jsexpressknex.js

NodeJS - Cannot set Headers after they are sent to the client


So I've searched around and found out that to fix the said issue, I have to return after sending a response. But my problem is, even though I have return, I still have the error.

const dbEditCourse = (req, res, db, logger) => {
    let {
        origCourse, code, description, type
    } = req.body;
    if (!code || !description || !type) {
        res.json({
            haveEmpty: true
        });
        return;
    }
    db.transaction((trx) => {
            db.select('*').from('course_strand').where('code', '=', code)
                .then(data => {
                    if (data[0]) {

                        //error happens in this block of code
                        res.json({
                            isSuccess: false
                        });
                        return;
                        //i also tried return res.json({ isSuccess: false });

                    }
                    //wrapping this in 'else' also does not work
                    return db('course_strand')
                        .returning('*')
                        .where('code', '=', origCourse)
                        .update({ code, description, type })
                })
                .then(course => {
                    return db('activity_logs')
                        .returning('*')
                        .insert({
                            date: new Date(),
                            employee_id: req.session.emp_id,
                            module: "COURSE / STRAND",
                            activity: "EDIT"
                        })
                })
                .then(activity => {
                    if (activity[0]) {
                        res.json({
                            isSuccess: true
                        });
                        return;
                    } else {
                        res.json({
                            isSuccess: false
                        });
                        return;
                    }
                })
                .then(trx.commit)
                .catch(err => {
                    logger.error(err);
                    trx.rollback;
                    res.render('pages/error-500');
                });
        })
        .catch(err => logger.error(err));
}

module.exports = {
    dbEditCourse
}

What I'm doing to produce the error is, If the record is existing, it will go into the block of code above. Aside from that specific block of code, I don't encounter the error elsewhere. And the code is working fine even though I have the error.


Solution

  • You cannot break a promise chain with return keyword, all .then statements will be executed (exclude you throw an error in a .then), the res.json has been called many times.

    Handler all errors (include your error and system error) in catch block.

    In catch block, check the error is throwing by you or not to return the response.

    const dbEditCourse = (req, res, db, logger) => {
      let {
        origCourse, code, description, type
      } = req.body;
      if (!code || !description || !type) {
        res.json({
          haveEmpty: true
        });
        return;
      }
    
      // util throw a error
      const breakWithMyError = () => {
        throw new Error("MY_ERROR");
      }
    
      db.transaction((trx) => {
        db.select('*').from('course_strand').where('code', '=', code)
          .then(data => {
            if (data[0]) {
    
              //error happens in this block of code
              breakWithMyError();
              //i also tried return res.json({ isSuccess: false });
    
            }
            //wrapping this in 'else' also does not work
            return db('course_strand')
              .returning('*')
              .where('code', '=', origCourse)
              .update({ code, description, type })
          })
          .then(course => {
            return db('activity_logs')
              .returning('*')
              .insert({
                date: new Date(),
                employee_id: req.session.emp_id,
                module: "COURSE / STRAND",
                activity: "EDIT"
              })
          })
          .then(activity => {
            // revert logic, we check for error case first
            if (!activity[0]) {
              breakWithMyError();
            }
          })
          .then(trx.commit)
          .then(() => {
            // finally you can run to here without any error
            res.json({
              isSuccess: true
            });
          })
          .catch(err => {
            // If you any error, the error comes form `breakWithMyError` or any things.
            if (err.message === "MY_ERROR") {
              // the error throw by `breakWithMyError`
              return res.json({
                isSuccess: false
              });
            }
            logger.error(err);
            trx.rollback;
            // Why you return a html page in failed case? `res.status(500).json({message: "Internal server!"});`
            res.render('pages/error-500');
          });
      })
        .catch(err => logger.error(err));
    }
    
    module.exports = {
      dbEditCourse
    }