Cannot set headers after they are sent to the client with express-validator, express, mongoose and Next.js

10,625

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
Share:
10,625

Related videos on Youtube

Antonio Pavicevac-Ortiz
Author by

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, 2022

Comments

  • Antonio Pavicevac-Ortiz
    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
      Antonio Pavicevac-Ortiz over 4 years
      Could you show me?
    • Nick
      Nick over 4 years
      Updated my answer to provide more detail
    • Nick
      Nick over 4 years
      Added update 2 to my previous answer. Hopefully this should work for you now.
  • Antonio Pavicevac-Ortiz
    Antonio Pavicevac-Ortiz over 4 years
    Thanks Nick! I’ll try that when I get home!
  • Antonio Pavicevac-Ortiz
    Antonio Pavicevac-Ortiz over 4 years
    Hey Nick! Thanks for the help, but based on the console.log's its working in reverse?! Its trying to save the request and then checks if the email exists...?
  • Nick
    Nick over 4 years
    Sorry for the late response. I was out of town this past weekend. Hopefully update 2 should resolve your issue :)
  • Yilmaz
    Yilmaz about 4 years
    { success: true } are you returning this even though you get error.
  • w. Patrick Gale
    w. Patrick Gale about 3 years
    So 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.