Finish Server-Side Validation

Introduction

Arriving at this activity assumes the successful completion of the previous server-side validation activity. This activity will add two final components to the process: adding a check to prevent email addresses from being duplicated in the database, and making the registration form inputs "sticky".

Video Overview

The video provides a general overview of the activity, but does not contain the detail needed to complete each process. Watch the video to obtain a general idea, but follow the written steps to complete the activity. This is the Transcript of the video.

Create a Custom Function

We will create a check function just for the email address. It will check the value of the account_email variable, after having been checked using the previous method, to see if it already exists in the database. If so, it will produce an error, stop the registration process, and the client will be informed.

  1. Find and open the account-model.js file in the models folder.
  2. At the bottom of the existing functions, but before the module.exports statement, create some empty space.
  3. Create a new asynchronous function named "checkExistingEmail(account_email)".
  4. The function should look something like this:
    /* **********************
     *   Check for existing email
     * ********************* */
    async function checkExistingEmail(account_email){
      try {
        const sql = "SELECT * FROM account WHERE account_email = $1"
        const email = await pool.query(sql, [account_email])
        return email.rowCount
      } catch (error) {
        return error.message
      }
    }
  5. Hopefully, this function looks familiar. It queries the database to see if a record exists with the same email that is being submitted. It returns the count of rows found. Anything greater than zero means the email already exists in the database.
  6. Add the function to the exports object at the bottom of the model.
  7. Check that no warning or errors exist in the model file.
  8. Save the file.

Apply the Email Check Function

  1. Find and open the account-validation file that you worked with in the previous activity.
  2. At the top, require the "account-model":
    const accountModel = require("../models/account-model")
  3. Scroll down to the check "account_email" rule.
  4. Modify the rule by adding the following code directly beneath the existing statements. When done, the entire rule should look like this:
    // valid email is required and cannot already exist in the database
    body("account_email")
      .trim()
      .isEmail()
      .normalizeEmail() // refer to validator.js docs
      .withMessage("A valid email is required.")
      .custom(async (account_email) => {
        const emailExists = await accountModel.checkExistingEmail(account_email)
        if (emailExists){
          throw new Error("Email exists. Please log in or use different email")
        }
      }),
  5. An Explanation

  6. Lines 1-6 - the existing code from the original rule. [Important! Note that the comma is gone from the end of line 6.]
  7. Line 7 - creates a "custom" check. Within the custom check an asyncronous, arrow function is created and the "account_email" variable is a parameter.
  8. Line 8 - calling the function from the model and collecting the value returned (should be 0 or 1)
  9. Line 9 - an "if" control structure to check the result. Remember that "0" is FALSE, while any other value is TRUE.
  10. Line 10 - throws an error and an error message indicating the email already exists and cannot be reused if the row count is 1. Note that this is a separate error message that applies only if the email exists.
  11. Line 11 - ends the "if" control structure.
  12. Line 12 - ends the arrow function and custom check. The comma is added to the end of the line to separate this rule from the next rule in the array.
  13. Check that there are no warnings or errors.
  14. Save the file.

Make the form "Sticky"

Up to this point, if the form was not filled in correctly, we created an error array, and returned the errors and the account names and email variables to the view via a render function. In the view, we built a method to display the errors, but the site visitor had to start over from scratch as they filled in the registration form. To provide a better user experience, we will make the form "sticky"; meaning that the values that they typed originally will appear back in the form input fields.

  1. Find and open the registration view.
  2. In the form, locate and alter the account_firstname input to look like this example:
    <input type="text" name="account_firstname" id="accountFirstname" required value="<%= locals.account_firstname %>">
  3. When you are sure your code is correct and no errors are being reported in your syntax, repeat this process for the account_lastname and account_email inputs.
  4. Do not do this for the password field! We always make the visitor re-type the password - Always!

An Explanation

The variable name should be clear enough, however the "locals" keyword may be unfamiliar. Express.js supports the storage of variables for use throughout the life of the application (e.g. app.locals) or through the entire request - response cycle (e.g. res.locals). In our code, we stored the variables as part of the validation process, and can now reuse them to make the form sticky. You will notice that we simply used "locals", which is a shorthand method for "res.locals". An added benefit is that if there is no value, such as when the registration view is initially loaded, it does not throw an error because the variable is undefined.

Time to Test

Just as you did previously, navigate to the registration form and reload to make sure the browser has the updated version. Then, 1) fill out the form making at least one mistake, 2) click the novalidate! tool to turn off the client-side validation, 3) submit the form. The view should be returned, error(s) displayed and the form re-populated (except for the password field).

Validate Login Data

The login form is much simpler, as it only contains two inputs for the email and password. We want to implement client-side and server-side checks to it as we did to the registration form.

Missing Login Process

Currently, we do not have a login process in place in the accountRoute or accountController files. To temporarily handle this situation, to allow for testing the server-side validations, add the following code to the accountRoute file below the existing routes, but before the module.exports statement:

// Process the login attempt
router.post(
  "/login",
  (req, res) => {
    res.status(200).send('login process')
  }
)

Ensure that there are no warnings or errors. Save the file.

An Explanation

Check the login Form

Make sure that the login view's form has the correct method and action to reach the route you just built.

  1. Find and open the login view.
  2. Find and check the opening <form> tag for the correct method and action attributes.
  3. If needed, alter the code to look like this:
    <form id="loginForm" action="/account/login" method="post">

Ensure that there are no warnings or errors. Save the file.

Add the Validations

As a team building exercise, work with your learning team to repeat the process you just did to validate the registration form, but do so for the login form - using both client-side and server-side validations. Remember that much of the code you wrote for the registration validations, is the same as what you'll need for the login validation. Build the server-side rules and check functions in the same account-validation file. This has the benefit of keeping the account related validations in a single location for ease of maintenance and implementation. In addition, don't forget to add the errors display functionality to the login view, just as you did previously with the registration view.

Final Test

When done, be sure to test to ensure that the login form's client-side and server-side validations work. When done, be sure to shut down the server in a VSC terminal, with "Control + C".