Home > Software design >  response sent twice even after returning and using async await
response sent twice even after returning and using async await

Time:10-13

I am trying to send just one response back to the client if the validation has failed, but the code below that line gets executed and I get the error "cannot set headers after sent". why am I getting this error when I am using async/await?

exports.register = catchAsync(async (req, res, next) => {
        let toCreate = {}
        mapUsers(toCreate, req.body)
        await validateUser(toCreate, res)
        toCreate.password = await bcrypt.hashPassword(toCreate.password)
        let newUser = await User.create({...toCreate})
        res.status(200).json({
            msg: 'user created', newUser
        })
})

validate user function:

module.exports = async (user, res) => {
        if(!user.name) return res.status(400).json({ msg: 'name is required'})
        if(user.name.length < 5) {
            return res.status(400).json({ msg: 'name must have more than 5 characters'})
        }
}

CodePudding user response:

When the username is invalid, validateUser function will send a response. The creation of a user will happen anyways, since you are not stopping execution even if the user is invalid.

You could return true or false from validateUser and depending on that build up your response. Also validateUser has no async parts in it. So remove the async for simplicity and not needing to await it anymore:

Register

exports.register = catchAsync(async (req, res, next) => {
        let toCreate = {}
        mapUsers(toCreate, req.body)
        if(validateUser(toCreate, res) === false) {
            return
        }
        toCreate.password = await bcrypt.hashPassword(toCreate.password)
        let newUser = await User.create({...toCreate})
        res.status(200).json({
            msg: 'user created', newUser
        })
})

validateUser

module.exports = (user, res) => {
        if(!user.name) {
            res.status(400).json({ msg: 'name is required'})
            return false
         }
        if(user.name.length < 5) {
            res.status(400).json({ msg: 'name must have more than 5 characters'})
            return false
        }
        return true
}

An more clean solution to the problem could be a separate handler for validating usernames before creation. You could add following handler for username validation (and more) in the chain before your register hook.

   exports.validateUser = (req, res, next) => {
      // only perform the check on the register location. 
      // Maybe add a better check.. This is just for showing the idea
      if(req.url.indexOf('/register') > -1) {
          let toCreate = {}
          mapUsers(toCreate , req.body)
    
          
          // store the mapped users in the request for accessing it later!
          req.auth = req.auth || {} 
          req.auth.mappedUsers = toCreate
          
          if(!user.name) {
            res.status(400).json({ msg: 'name is required'})
            // return for not calling next! .
            return
         }
        if(user.name.length < 5) {
            res.status(400).json({ msg: 'name must have more than 5 characters'})
            // return for not calling next! .
            return
        }
      }
      
      // if it's not register location OR no checks failed, call the next handler!
      next()
   }

The final handler for creating a user would only be called when the username is valid! The registerHandler does also not need to be async. There is no async function in it called so keep it simple! . The new register handler:

exports.register = req, res, next) => {
        // the validateUser handler was called before and already 
        // mapped the user. If the code gets in here, the username is 
        // valid. 
        let toCreate = req.mappedUsers
        toCreate.password = await bcrypt.hashPassword(toCreate.password)
        let newUser = await User.create({...toCreate})
        res.status(200).json({
            msg: 'user created', newUser
        })
}

CodePudding user response:

You don't have any async code in the validate function so using await make no sence.

There's many ways to fix your code. One could be:

exports.register = catchAsync(async (req, res, next) => {
    let toCreate = {}
    mapUsers(toCreate, req.body)

    // no need to use await since the function is synchronous
    let err = validateUser(toCreate, res)
    if (err)
        return res.status(400).json({ msg: err })

    toCreate.password = await bcrypt.hashPassword(toCreate.password)
    let newUser = await User.create({...toCreate})
    res.status(200).json({
        msg: 'user created', newUser
    })
})

and the validate function

module.exports = (user) => {
    if(!user.name)
        return 'name is required';
    if(user.name.length < 5)
        return 'name must have more than 5 characters';
}
  • Related