KhoPhi
KhoPhi

Reputation: 9557

Express using the Router() params

Say I have this code using the Router() of express.

authRoute.param('token', function(req, res, next, token) {
    User.findOne({ 'local.resetPasswordToken': req.params.token, resetPasswordExpires: { $gt: Date.now() }}, function(err, user) {
        if (!user) {
            req.flash('error', 'error msg here');
            return res.redirect('/forgot');
        }
        req.user = user;
    })
    next();
})

authRoute.route('/reset/:token')
    .get(function(req, res) {
        res.render('reset', {
            user: req.user
        })
    })

I get this error:

_http_outgoing.js:346
    throw new Error('Can\'t set headers after they are sent.');
    ^
Error: Can't set headers after they are sent.
    at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:346:11)

Without the Router(), this is how it looks:

app.get('/reset/:token', function(req, res) {
  User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function(err, user) {
    if (!user) {
      req.flash('error', 'Password reset token is invalid or has expired.');
      return res.redirect('/forgot');
    }
    res.render('reset', {
      user: req.user
    });
  });
});

What am I doing wrong with the Router.param side? I read on the error message given, and I learn it happens when an attempt is made to set headers on response that's already been set.

Upvotes: 0

Views: 115

Answers (1)

undefined
undefined

Reputation: 2214

You're calling next() in authRout.param immediately, thus allowing other middleware's to, at some point, end up calling res.end() (res.render, res.send, etc) before the Mongoose callback has been invoked. My assumption is you're getting this error when allowing middleware's execute (previously explained) then invoking res.redirect('/forgot') after one of the middlewares has already "ended" the response.

The solution is to wait for your db response before continuing down the middleware chain, simply an async issue. Consider the folllowing:

authRoute.param('token', function(req, res, next, token) {
    User.findOne({ 'local.resetPasswordToken': req.params.token, resetPasswordExpires: { $gt: Date.now() }}, function(err, user) {
        // pass error to middlewares
        if (err) return next(err)
        // Stop the middleware chain i.e. return res.redirect
        if (!user) {
            req.flash('error', 'error msg here');
            return res.redirect('/forgot');
        }
        req.user = user;
        // important! call `next()` after mongo response
        next()
    })
})

This isn't happening in the single request handler (your latter example) because you're properly handling async - not ending the http response before the Mongoose callback is invoked.

Upvotes: 2

Related Questions