14. Creating User Accounts


In this tutorial we create an endpoint to allow an app to create a new user account.  For our discussion we assume there exists a User model that contains

  • a username property that is mapped to a String and has the unique property set to true,
  • a password property that is mapped to a String, and
  • an authTokens property that is mapped to an array of Strings.

Inside your user router file copy the code below to create an endpoint that clients can use to create user accounts.

router.post('/user', async (req, res) => {
  try {
    const user = new User(req.body)
    await user.save()

    res.status(201).send()
  }
  catch (err) {
    console.log(err)
    if (err.code === 11000) {
      console.log('Duplicate account')
      return res.status(409).send(err)
    }
    else if (err.name === 'ValidationError'){
      console.log('Validation error')
      return res.status(400).send(err)
    }
    else {
      res.status(500).send(err)
    }
  }
})

The endpoint requires the client to send the user’s account information (e.g. username and password) in the body of the request. 

Line 3 creates a user document using the data passed in the body of the request.

Line 4 attempts to save the document in the database.

If an exception was not thrown when trying to save the document then the server returns a response with a 201 status code (line 6).  

Otherwise we send a response with an appropriate status code and the error object (lines 8-21).

Add a Pre-Save Hook to the User Schema

Mongoose allows us to write functions that execute before or after various other mongoose functions are called.  These are called middleware functions.

For security reasons we don’t want to save the user’s password as plain-text in the database.  Instead, we’ll store a hash of the user’s password.  To accomplish this, we’ll add a pre-save middleware function that executes before User documents are saved in the database.  The function will check to see if the password field has been modified, and if so, replaces the value in the password field with a hashed string.

If you haven’t already, install the bcrypt npm module in your node project.  

Then, at the top of the file containing your User schema, import bcrypt.

import bcrypt from 'bcrypt'

In your User schema file, before calling mongoose.model(), add the following save pre-hook.

userSchema.pre("save", async function (next) {
  const user = this;

  if (user.isModified("password")) {
    user.password = await bcrypt.hash(user.password, 8);
  }

  next();
});

Whenever we call save() we call it on a User document (e.g. await user.save()).  The pre-hook shown above is an instance method and so inside the hook the this keyword (line 2) holds a reference to the document on which save() is called.

Lines 4-6 check to see if the document’s password field has been modified since it was last saved.  If so, then the password field is set to a hash of the password.  The second argument is a salt parameter used to hash the password.

Line 8 invokes either the next middleware function or save() if no other middleware functions exist.

Test the Endpoint

Open Compass and connect to your app’s database.  Once connected, in the pane on the left side, drop your existing collections by clicking on the trash can icon next to the name of the database that holds your collections (mine is named test).

Start your API server in VSC.

Start up Postman and create a (or edit an existing) request to create a new user account.  Your request should include username and password properties in the request’s body.  Save your request configuration.  Press the Send button to send a request.  You should receive a response with a 201 status code.  If successful, attempt to create a second user with the same username.  You should receive a response with a 409 status code.

Use Compass to inspect your User collection.  You should see a document for the user you created.  Verify that that password is not in plaintext.