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';
}