Cannot set headers after they are sent to the client with express-validator, express, mongoose and Next.js
The error 'Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client' means that res.send/json/redirect fired more than once during the request. Just at a first glance, this code block has the potential of causing this error.
.get(function(req, res) {
UserModel.find({}, (err, users) => {
if (err) res.status(500).send(err)
res.json(users)
})
})
Without having looked on the exact request being made, I recommend putting console.log lines everywhere to see if you can pinpoint what happens during runtime.
UPDATE
.get(function(req, res) {
UserModel.find({}, (err, users) => {
// If there's an error, this will fire and continue
if (err) res.status(500).send(err)
// This fires next even if there is an error
res.json(users)
})
})
This is a possibility of what can be causing that issue. There is a potential of res firing twice. The fix would be to add return. This ensures that the code does not continue.
.get(function(req, res) {
UserModel.find({}, (err, users) => {
if (err) return res.status(500).send(err)
return res.json(users)
})
})
If this doesn't fix it, I recommend putting console.log statements after all arguments everywhere so you can pinpoint which line it's failing on. For Example:
router
.route('/registration')
.get(function(req, res) {
console.log(0)
UserModel.find({}, (err, users) => {
console.log(1)
if (err) res.status(500).send(err)
console.log(2)
res.json(users)
console.log(3)
})
})
.post(body('username').custom(value => {
console.log(4)
UserModel.findOne({ 'email': value }).then(user => {
console.log(5)
if (user) {
console.log(6)
return Promise.reject('E-mail already in use');
}
});
}), async(req, res, next) => {
console.log(7)
try {
let newUser = new UserModel(req.body)
let savedUser = await newUser.save(err => {
if (err) return res.json({ success: false, error: err })
console.log(8)
return res.json({ success: true })
})
console.log(9)
if (savedUser) return res.redirect('/users/registration?success=true');
console.log(10)
return next(new Error('Failed to save user for unknown reasons'))
} catch (err) {
return next(err)
}
})
UPDATE 2
So I finally put the code in my editor and noticed that you didn't return your promise in your validator, which may be the reason why you're running into everything async. I added the comment to show you where I returned the promise. Hopefully, this will work for you now :)
router.route('/registration')
.get(function(req, res) {
UserModel.find({}, (err, users) => {
if (err) res.status(500).send(err)
res.json(users)
})
})
.post(body('username').custom(value => {
return UserModel.findOne({ 'email': value }).then(user => { // Return Promise
if (user) {
return Promise.reject('E-mail already in use');
}
});
}), async(req, res, next) => {
try {
let newUser = new UserModel(req.body)
let savedUser = await newUser.save(err => {
if (err)
return res.json({ success: false, error: err })
return res.json({ success: true })
})
if (savedUser)
return res.redirect('/users/registration?success=true');
return next(new Error('Failed to save user for unknown reasons'))
} catch (err) {
return next(err)
}
})
module.exports = router
Related videos on Youtube
Antonio Pavicevac-Ortiz
Hi all! I am currently a full-time Web Developer who lives in Queens NYC! I love running and spending time with my rascally little daughter! :)
Updated on June 04, 2022Comments
-
Antonio Pavicevac-Ortiz almost 2 years
I am building a login/registration form using express-validator and mongoose in next.js.
Heard the best practice was to sanitize your data on the front and backend.
I have some validations on the frontend (i.e. checking if an email via Regex and making sure a password in a particular length).
But now I'd like to use Custom validator to check if a email exists in my mongodb database.
.post(body('username').custom(value => { UserModel.findOne({ 'email': value }).then(user => { if (user) { return Promise.reject('E-mail already in use'); } }); }),
This is the rest of my code:
var router = require('express').Router() var UserModel = require('../models/UserModel') var { body } = require('express-validator'); router .route('/registration') .get(function(req, res) { UserModel.find({}, (err, users) => { if (err) res.status(500).send(err) res.json(users) }) }) .post(body('username').custom(value => { UserModel.findOne({ 'email': value }).then(user => { if (user) { return Promise.reject('E-mail already in use'); } }); }), async(req, res, next) => { try { let newUser = new UserModel(req.body) let savedUser = await newUser.save(err => { if (err) return res.json({ success: false, error: err }) return res.json({ success: true }) }) if (savedUser) return res.redirect('/users/registration?success=true'); return next(new Error('Failed to save user for unknown reasons')) } catch (err) { return next(err) } }) module.exports = router
And this is the error I'm getting:
Error: Failed to save user for unknown reasons at router.route.get.post (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/server/users/index.js:34:25) at process._tickCallback (internal/process/next_tick.js:68:7) Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:470:11) at ServerResponse.header (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/response.js:767:10) at ServerResponse.send (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/response.js:170:12) at ServerResponse.json (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/response.js:267:15) at /Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/server/index.js:108:17 at Layer.handle_error (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/layer.js:71:5) at trim_prefix (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:315:13) at /Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:335:12) at next (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:275:10) at Layer.handle_error (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/layer.js:73:5) at trim_prefix (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:315:13) at /Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:335:12) at Immediate.next (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:275:10) at Immediate.<anonymous> (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:635:15) at runCallback (timers.js:706:11) at tryOnImmediate (timers.js:676:5) at processImmediate (timers.js:658:5)
Also do I even need this when Mongoose provides when designing models/schema?
var mongoose = require('mongoose') var emailValidator = require('email-validator') var bcrypt = require('bcrypt') // hashing function dedicated for passwords const SALT_ROUNDS = 12 var UserSchema = new mongoose.Schema( { username_email: { type: String, required: true, lowercase: true, index: { unique: true }, // I mean this! validate: { validator: emailValidator.validate, message: props => `${props.value} is not a valid email address` } }, password: { type: String, required: true, trim: true, index: { unique: true }, minlength: 8 } }, { timestamps: true } ) UserSchema.pre('save', async function preSave(next) { var user = this var hash if (!user.isModified('password')) return next() try { hash = await bcrypt.hash(user.password, SALT_ROUNDS) user.password = hash return next() } catch (err) { return next(err) } }) UserSchema.methods.comparePassword = async function comparePassword(candidate) { return bcrypt.compare(candidate, this.password) }; module.exports = mongoose.model('User', UserSchema)
And if I don't does that mean checking if the email exists should be moved to the frontend? And If that's the case how would I approach that?
UPDATE
I tried Nick's suggestion but not sure why I'm still getting
`Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
` These are the updated routes:
router .route('/registration') .get(function(req, res) { console.log(0) UserModel.find({}, (err, users) => { console.log(1) if (err) res.status(500).send(err) console.log(2) return res.json(users) console.log(3) }) }) .post(body('email').custom(value => { console.log(4) UserModel.findOne({ 'email': value }).then(user => { console.log(5) if (user) { console.log(6) return Promise.reject('E-mail already in use'); } }); }), async(req, res, next) => { console.log(7) try { let newUser = new UserModel(req.body) let savedUser = await newUser.save(err => { if (err) return res.json({ success: false, error: err }) console.log(8) return res.json({ success: true }) }) console.log(9) if (savedUser) return res.redirect('/users/registration?success=true'); console.log("savedUser ", savedUser); console.log(10) return next(new Error('Failed to save user for unknown reasons')) } catch (err) { return next(err) } }) Note that pages will be compiled when you first load them. GET /_next/static/webpack/d691821e71bf01c860e6.hot-update.json 404 299.194 ms - 1862 GET /_next/static/webpack/42c7a9cb77dec12fc8a3.hot-update.json 200 40.276 ms - 35 4 7 9 savedUser undefined 10 POST /users/registration 200 21.490 ms - 422 Error: Failed to save user for unknown reasons at router.route.get.post (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/server/users/index.js:42:25) at process._tickCallback (internal/process/next_tick.js:68:7) Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:470:11) at ServerResponse.header (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/response.js:767:10) at ServerResponse.send (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/response.js:170:12) at ServerResponse.json (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/response.js:267:15) at /Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/server/index.js:108:17 at Layer.handle_error (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/layer.js:71:5) at trim_prefix (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:315:13) at /Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:335:12) at next (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:275:10) at Layer.handle_error (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/layer.js:73:5) at trim_prefix (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:315:13) at /Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:284:7 at Function.process_params (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:335:12) at Immediate.next (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:275:10) at Immediate.<anonymous> (/Users/antoniopavicevac-ortiz/Dropbox/developer_folder/hillfinder/node_modules/express/lib/router/index.js:635:15) at runCallback (timers.js:706:11) at tryOnImmediate (timers.js:676:5) at processImmediate (timers.js:658:5) 5 6 (node:68936) UnhandledPromiseRejectionWarning: E-mail already in use (node:68936) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2) (node:68936) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. ^C
-
Antonio Pavicevac-Ortiz over 4 yearsCould you show me?
-
Nick over 4 yearsUpdated my answer to provide more detail
-
Nick over 4 yearsAdded update 2 to my previous answer. Hopefully this should work for you now.
-
-
Antonio Pavicevac-Ortiz over 4 yearsThanks Nick! I’ll try that when I get home!
-
Antonio Pavicevac-Ortiz over 4 yearsHey Nick! Thanks for the help, but based on the
console.log's
its working in reverse?! Its trying to save therequest
and then checks if the email exists...? -
Nick over 4 yearsSorry for the late response. I was out of town this past weekend. Hopefully update 2 should resolve your issue :)
-
Yilmaz about 4 years{ success: true } are you returning this even though you get error.
-
w. Patrick Gale about 3 yearsSo helpful! I had assumed that
res.send()
from in an API would automatically return. I guess it does not. Adding a return statement where I want the API to stop fixed the API errors for me.