问题
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.
回答1:
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 {...
回答2:
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
来源:https://stackoverflow.com/questions/58660684/how-do-i-check-if-email-already-exists-using-mern