camstar915
camstar915

Reputation: 153

MEAN Stack - Cannot set headers after they are sent to client

This has been driving me crazy.. The app I'm working on is throwing the "Cannot set headers after they are sent to the client" error. I'm very new to writing anything on the back-end, and can't figure out why this is being thrown. I've read through all of the related posts on the issue and STILL can't see what's wrong. Please help me find what I'm missing.

This is the route I'm trying to access, userRoutes.js:

router.post('/login', (req, res, next) => {
  console.log('1');
  let fetchedUser;
  HaulerUser.findOne({ username: req.body.username })
    .then(user => {
      console.log('2');
      if (!user) {
        return res.status(401).json({
          message: 'Auth failed'
        });
      }
      console.log('3');
      fetchedUser = user;
      return bcrypt.compare(req.body.password, user.password);
    })
    .then(result => {
      console.log('4');
      if(!result) {
        return res.status(401).json({
          message: 'Auth failed'
        });
      }
      const token = jwt.sign(
        { username: fetchedUser.username, userId: fetchedUser._id },
        'secret_this_should_be_longer',
        { expiresIn: '1hr' }
      );
      console.log('5');
      res.status(200).json({
        token: token,
        expiresIn: 3600,
        userId: fetchedUser._id
      });
      console.log('6');
    })
    .catch(err => {
      console.log('7');
      return res.status(401).json({
        message: 'Invalid authentication credentials',
        error: err
      });
    });
});

Also, it's only printing 1, 2, 4, and 7 to the console????

This is the main NodeJS code.

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

app.use((req, res, next) => {
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  res.setHeader(
    'Access-Control-Allow-Methods',
    'GET, POST, PATCH, PUT, DELETE, OPTIONS'
  );
  next();
});

app.use('/api/user', userRoutes);
app.use('/api/manifests', manifestsRoutes);
app.use('/api/sites', sitesRoutes);

I'm a bit over my head with this one... I'm sure it's something small... please help.....

Upvotes: 1

Views: 61

Answers (1)

Joe Doyle
Joe Doyle

Reputation: 6383

As the comments have mentioned, you are calling res.json() more than once, which is what the error is saying.

You're running into a common issue that folks hit when learning how to chain Promises. Returning res.status(401).json({...}) does not reject the Promise and instead returns the result of res.status(401).json({...}) to the next .then() block which in turn does the same down the stack when the user is not found.

You have a few options to fix this but the most common way would be to use Exceptions to exit out of the Promise chain. Instead of res.status(401).json({...}) in each if block, do throw new Error('{an error message}'). This will exit the chain and jump to your .catch() which sends then calls res.status(401).json({...}). You can then also log the specific error message while returning the generic message to the caller.

router.post('/login', (req, res, next) => {
  console.log('1');
  let fetchedUser;
  HaulerUser.findOne({ username: req.body.username })
    .then(user => {
      console.log('2');
      if (!user) {
        throw new Error(`User '${req.body.username}' not found`);
      }
      console.log('3');
      fetchedUser = user;
      return bcrypt.compare(req.body.password, user.password);
    })
    .then(result => {
      console.log('4');
      if(!result) {
        throw new Error(`Password did not match`);
      }
      const token = jwt.sign(
        { username: fetchedUser.username, userId: fetchedUser._id },
        'secret_this_should_be_longer',
        { expiresIn: '1hr' }
      );
      console.log('5');
      res.status(200).json({
        token: token,
        expiresIn: 3600,
        userId: fetchedUser._id
      });
      console.log('6');
    })
    .catch(err => {
      console.log('7');
      console.log(err);
      res.status(401).json({ // You don't need a return here
        message: 'Invalid authentication credentials',
        error: err
      });
    });
});

If you are on Node 8 or higher, you could use async/await instead of .then()/.catch() and your code would behave how you expected.

Upvotes: 1

Related Questions