2
votes

I have the following code for signing up a user. Where I first validate the user input. Secondly I check to see if the user already exists, if yes it should return with response 400. If not go to step 3 and add the new user. Finally in step 4 return the newly created entry. Logically, it works and adds data to database correctly, however it always responds back with 'User already exists' on postman (from step 2) even if it's a new user which has correctly added the user to the db. Which makes me think the third step is being done before a response in step 2 can be sent, which would mean I have not chained the promise correctly. Also the new user is never sent back as response, which I think is because I have not used Promise.then() together with user.save() correctly. I also get the following error (posted after the code), which I understand means I am trying to send a second response after a first has already been sent. I can solve this problem with async and await but want to learn how to do it this way. Thanks, any help is appreciated.

const { User, validateUser } = require('../models/userModel');
const mongoose = require('mongoose');
const express = require('express');
const router = express.Router();

router.post('/', (req, res) => {
    return Promise.resolve()
        .then(() => {
            //Step 1: validae the user input and if there is an error, send 400 res and error message
            console.log('My user post body req::', req.body);
            const { error } = validateUser(req.body); //this is using Joi.validate() which has a error property if errors are found
            if (error) {
                return res.status(400).send(error.details[0].message);
            }
        })
        .then(() => {
            //step 2: check if user already exists, if yes send res 400
            let user = User.findOne({ email: req.body.email });
            if (user) {
                return res.status(400).send('User already exists');
            }
        })
        .then(() => {
            //Step 3: enter new user into the database
            user = new User({
                name: req.body.name,
                email: req.body.email,
                password: req.body.password
            });
            return user.save();
        })
        .then((result) => {
            //step 4: return the newly added user
            return res.status(200).send(result);
        })
        .catch((error) => {
            console.log('Error Adding new User', error);
        });
});

module.exports = router;

I get the following error message from the catch. Even though I am I am returning with every response

Error Adding new User Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (_http_outgoing.js:494:11)
    at ServerResponse.header (/home/ssaquif/WebDevProjects/movie-reviews-backend/node_modules/express/lib/response.js:771:10)
    at ServerResponse.send (/home/ssaquif/WebDevProjects/movie-reviews-backend/node_modules/express/lib/response.js:170:12)
    at ServerResponse.json (/home/ssaquif/WebDevProjects/movie-reviews-backend/node_modules/express/lib/response.js:267:15)
    at ServerResponse.send (/home/ssaquif/WebDevProjects/movie-reviews-backend/node_modules/express/lib/response.js:158:21)
    at /home/ssaquif/WebDevProjects/movie-reviews-backend/routes/users.js:35:27
    at processTicksAndRejections (internal/process/task_queues.js:93:5) {
  code: 'ERR_HTTP_HEADERS_SENT'
3
You create new users even if they already exist ...Jonas Wilms
@JonasWilms I believe I dont. It supposed to respond back with a 400 when the user already exists and I tested it, duplicate users are not created in the database. But that could be becuase of my validation while creating the schemassaquif
Yup, you respond with a 400, but create the user neverthelessJonas Wilms

3 Answers

1
votes

You don't need to use Promise.resolve in your route. You just need a chain of then blocks, in which you need to return a value to the next one.

I refactored your code like this:

router.post("/", (req, res) => {
    //Step 1: validate the user input and if there is an error, send 400 res and error message
    console.log("My user post body req::", req.body);
    const { error } = validateUser(req.body);
    if (error) {
      return res.status(400).send(error.details[0].message);
    }

    //step 2: check if user already exists, if yes send res 400
    User.findOne({ email: req.body.email })
      .then(user => {
        if (user) {
          return res.status(400).send("User already exists");
        }
        return;
      })
      .then(() => {
        //Step 3: enter new user into the database
        let user = new User({
          name: req.body.name,
          email: req.body.email,
          password: req.body.password
        });
        return user.save();
      })
      .then(result => {
        //step 4: return the newly added user
        return res.status(200).send(result);
      })
      .catch(error => {
        console.log("Error Adding new User", error);
        res.status(500).send("Error");
      });
  });

You will have a result like this when a user successfully registers:

{
    "_id": "5dd65df52f7f615d8067150d",
    "name": "ssaquif",
    "email": "test@test.com",
    "password": "123123",
    "__v": 0
}

And when an existing email is used, the response will be like this with statusCode 400.

User already exists
1
votes

You could solve this somehow by chaining promises correctly in a more complicated way, or you use async / await and get rid of all those problems:

router.post('/', async (req, res) => {
 try {
  //Step 1: validae the user input and if there is an error, send 400 res and error message
  console.log('My user post body req::', req.body);
  const { error } = validateUser(req.body); //this is using Joi.validate() which has a error property if errors are found
  if (error) {
     return res.status(400).send(error.details[0].message);
  }

  //step 2: check if user already exists, if yes send res 400
  let user = await User.findOne({ email: req.body.email });
  if (user) {
    return res.status(400).send('User already exists');
  }

  //Step 3: enter new user into the database
  user = new User({
            name: req.body.name,
            email: req.body.email,
            password: req.body.password
  });
  await user.save();


  //step 4: return the newly added user
  return res.status(200).send(user);
 } catch(error) {
    // Report error internally
    return res.status(500).send("Something bad happened");
 } 
});

The main problem with your code is that returning from a .then callback will continue executing the next .then callback. Therefore you try to set the headers status multiple times (but that's your smallest problem).

0
votes

If you look at the error message "Cannot set headers after they are sent to the client" it means you are trying to send something over the response object twice which are not possible. Try log something right before each time you send something as a response and see which two are being called.

Instead of returning the res.status(400).send promise, try call it normally and then return a rejected promise or throw an error instead.