简体   繁体   中英

How do I check if email already exists using MERN

I have a registration view and I'm trying to check whether the email already exists, I've undone the react code so you can get a good idea of the structure.

The emails are set as unique in the schema.

AuthController

const { check, validationResult } = require("express-validator");
const jwt = require("jsonwebtoken");
const passport = require("passport");
require("../passport");

const Users = require("../models/user");

let validations = [
  check("email")
    .isEmail()
    .withMessage("The email you have entered is not valid")
    .contains("@")
    .withMessage("The email you have entered does not contain an @"),

  check("password")
    .isLength({ min: 5 })
    .withMessage("The password must have at least 5 characters")
];

// Throw error if the key doesn't exist
if (!process.env.JWT_SECRET) {
  console.error("Cannot find JWT key");
}

function generateWebToken(user) {
  return jwt.sign(user, process.env.JWT_SECRET, {
    subject: user.email,
    expiresIn: "7d",
    algorithm: "HS256"
  });
}

/* POST register a user if one doesn't already exist */
module.exports.register = [
  ...validations,
  (req, res) => {
    // Get validation errors from the request
    const errors = validationResult(req);
    // Return the errors
    if (!errors.isEmpty()) {
      return res.status(422).json({ error: errors.array() });
    }

    let hashedPassword = Users.hashPassword(req.body.password);

    Users.findOne({ email: req.body.email })
      .then(function(user) {
        if (user) {
          return res
            .status(400)
            .send(`An account with the email ${req.body.email} already exists`);
        } else {
          Users.create({
            email: req.body.email,
            password: hashedPassword
          })
            .then(function(user) {
              res.status(201).json(user);
            })
            .catch(function(err) {
              console.error(err);
              res.status(500).send(`Error ${err}`);
            });
        }
      })
      .catch(function(err) {
        console.error(err);
        res.status(500).send(`Error ${err}`);
      });
  }
];

Register.js (react component)

import React, { Component } from "react";

const initalState = {
  email: "",
  password: "",
  emailErr: "",
  passwordErr: ""
};

class Register extends Component {
  constructor(props) {
    super(props);
    this.state = {
      initalState,

      successMsg: ""
    };

    this.handleSubmit = this.handleSubmit.bind(this);
  }

  validate = () => {
    let emailErr = "";
    let passwordErr = "";

    // Email validation
    if (!this.state.email) {
      emailErr = "Please enter an email";
    }

    // Password validation
    if (!this.state.password) {
      passwordErr = "Please enter your password";
    }

    if (emailErr || passwordErr) {
      this.setState({ emailErr, passwordErr });
      return false;
    }

    return true;
  };

  onEmailChange(event) {
    this.setState({ email: event.target.value });
  }

  onPasswordChange(event) {
    this.setState({ password: event.target.value });
  }

  handleSubmit(event) {
    event.preventDefault();
    const isValid = this.validate();

    if (isValid) {
      fetch("/api/auth/register", {
        method: "POST",
        body: JSON.stringify(this.state),
        headers: {
          Accept: "application/json",
          "Content-Type": "application/json"
        }
      })
        .then(res => res.json())
        .then(this.setState({ successMsg: true }), this.setState(initalState));
    }
  }

  render() {
    return (
      <>
        <div className='block md:flex md:flex-column h-full'>
          <div className='p-12 w-full text-center text-gray-800'>
            <h1 className='title'>Register</h1>

            {this.state.successMsg && (
              <div
                className='w-full m-auto max-w-lg mb-10 bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative'
                role='alert'
              >
                <strong className='font-bold'>Holy smokes! </strong>
                <span className='block sm:inline'>
                  Account has been created
                </span>
              </div>
            )}

            <form className='w-full m-auto max-w-lg'>
              <div className='flex flex-wrap -mx-3'>
                <div className='w-full px-3 mb-6'>
                  <label htmlFor='email'>Email Address</label>

                  <input
                    id='email'
                    type='text'
                    placeholder='johndoe@example.co.uk'
                    value={this.state.email || ""}
                    onChange={this.onEmailChange.bind(this)}
                  />

                  <p className='my-2 text-red-500 text-xs'>
                    {this.state.emailErr}
                  </p>
                </div>
              </div>

              <div className='flex flex-wrap -mx-3'>
                <div className='w-full px-3 mb-6'>
                  <label htmlFor='password'>Password</label>

                  <input
                    id='password'
                    type='password'
                    placeholder='*********'
                    value={this.state.password || ""}
                    onChange={this.onPasswordChange.bind(this)}
                  />

                  <p className='my-2 text-red-500 text-xs'>
                    {this.state.passwordErr}
                  </p>
                </div>
              </div>

              <div className='flex'>
                <button
                  className='btn'
                  type='button'
                  onClick={this.handleSubmit.bind(this)}
                >
                  Send
                </button>
              </div>
            </form>
          </div>
        </div>
      </>
    );
  }
}

export default Register;

Not sure if it's possible just to pass the express validations or what the best solution for this is.

The code above looks fine as this line of code explicitly checks for email duplications in MongoDB

>if (user) {
        >  return res
          >  .status(400)
          >  .send(`An account with the email ${req.body.email} already exists`);
       > } else {...

Here is a full solution to display errors from register api (both express-validations and user is already registered error)

I tried to mimic your api and react app, but heavily refactored:

1-) In api we need to convert express validation errors in a readable way:

Normally its validation errors are in an array like this:

{
    "error": [
        {
            "value": "abcdef@net",
            "msg": "The email you have entered is not valid",
            "param": "email",
            "location": "body"
        },
        {
            "value": "12",
            "msg": "The password must have at least 5 characters",
            "param": "password",
            "location": "body"
        }
    ]
}

With the following code, I converted the error messages in this format to easily access in react app by field name:

{
    "errors": {
        "email": "The email you have entered is not valid",
        "password": "The password must have at least 5 characters"
    }
}

register route:

const { check, validationResult } = require("express-validator");
const bcrypt = require("bcryptjs");
const { User } = require("../models/user");
const express = require("express");
const router = express.Router();

let validations = [
  check("email")
    .isEmail()
    .withMessage("The email you have entered is not valid"),

  check("password")
    .isLength({ min: 5 })
    .withMessage("The password must have at least 5 characters")
];

router.post("/register", validations, async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    const err = {};
    errors.array().forEach(error => {
      err[error.param] = error.msg;
    });
    return res.status(422).json({ errors: err });
  }

  const { email, password } = req.body;

  try {
    let user = await User.findOne({ email });
    if (user) return res.status(400).send("User already registered.");

    user = new User({ email, password });
    const salt = await bcrypt.genSalt(10);
    user.password = await bcrypt.hash(user.password, salt);
    user = await user.save();

    res.send(user);
  } catch (err) {
    console.log(err);
    res.status(500).send("Something went wrong");
  }
});

module.exports = router;

In the react side I used axios instead of fetch since the error handling is far easier with axios:

import React, { Component } from "react";
import axios from "axios";

class App extends Component {
  state = {
    email: "",
    password: "",
    fieldErrors: {},
    registerError: null
  };

  handleSubmit = async e => {
    e.preventDefault();

    this.setState({
      fieldErrors: {},
      registerError: null
    })

    const { email, password } = this.state;

    try {
      const response = await axios.post(
        "https://express-validator-auth.herokuapp.com/api/users/register",
        { email, password }
      );
      console.log(response.data);
      alert("Registered");
    } catch (error) {
      if (error.response && error.response.data) {
        if (error.response.data.errors) {
          this.setState({
            fieldErrors: error.response.data.errors
          });
        } else {
          this.setState({
            registerError: error.response.data
          });
        }
      } else {
        console.log(error);
      }
    }
  };

  handleChange = e => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };

  render() {
    const { email, password, fieldErrors, registerError } = this.state;

    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <div className="form-group">
            <label>Email</label>
            <input
              type="text"
              className={
                !fieldErrors.email ? "form-control" : "form-control is-invalid"
              }
              name="email"
              value={email}
              onChange={this.handleChange}
            />
            {fieldErrors.email && (
              <div className="invalid-feedback">{fieldErrors.email}</div>
            )}
          </div>
          <div className="form-group">
            <label>Password</label>
            <input
              type="password"
              className={
                !fieldErrors.password
                  ? "form-control"
                  : "form-control is-invalid"
              }
              name="password"
              value={password}
              onChange={this.handleChange}
            />
            {fieldErrors.password && (
              <div className="invalid-feedback">{fieldErrors.password}</div>
            )}
          </div>
          <button type="submit" className="btn btn-primary">
            Submit
          </button>
          {registerError && (
            <div className="alert alert-danger" role="alert">
              {registerError}
            </div>
          )}
        </form>
        <br />
        {JSON.stringify(this.state)}
      </div>
    );
  }
}

export default App;

So when an validation error occurres form will look like this:

在此处输入图像描述

And when the "user is already registered error" occurres form validation will look like this:

在此处输入图像描述

The react app can be accessed from this codesandbox: https://codesandbox.io/s/gracious-mccarthy-kluor

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM