Why cannot react catch an error from the node? - node.js

I build a register page using React as Frontend, and Node Js as backend. However, when I try to check the delicate username. Axios from the Frontend doesn't show any error. I have written the catch in many different ways. But I still cannot find where the problem is. Could anyone help me out? Thanks!
Frontend
const handleSubmit = async (event) => {
event.preventDefault();
if (handleValidation()) {
await axios.post(registerRoute, {
username,
email,
password,
}).then((response) => {
console.log(response);
}).catch((error) => {
if (error.response.status === 11000) {
console.log(error.response.data);
}
})
navigate("/");
}
};
Backend
module.exports.register = async (req, res, next) => {
const { username, email, password } = req.body;
if (!username || typeof username !== 'string') {
return res.json({status: 'error', message: 'Invalid username'})
}
const usernameExit = await Users.findOne({username: username})
if (usernameExit) {
return res.status(11000).json({ message: "Username already exit" });
}
if (!password || typeof password !== 'string') {
return res.json({status: 'error', message: 'Invalid password'})
}
try {
const hashedPassword = await bcrypt.hash(password, 2);
const user = new Users({
username,
email,
password: hashedPassword,
});
user.save();
delete user.password;
return res.json({ status: true, user });
} catch (error) {
if (error.code === 11000) {
return res.status(11000).json({ message: "Username already exit" });
}
}
};

First things first. In your back end endpoint you need to add the word await in before saving the document since you're using async/await for this method.
await user.save();
Second, you won't be able to delete the user's password this way because this is a MongoDB document and not a JavaScript object. You can convert the document to be a JS object which was explained in this question below. After you convert it to a JS object then you do delete user.password, not before.
convert mongodb object to javascript object
Third, you should always follow the industry best practices and use one of the HTTPS codes based on the situation. This way you know that a MongoDB error code is coming from MongoDB and not your endpoint because you setup a different status code like 404 or 400 for any custom error handling that you are doing.
Here's the reference for the HTTPS codes - https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
If you want to catch the status in Axios, you need to set the status like #Yozi mentioned and follow the approach below
return res.status(400).json({message: "Some message here", user});
This way Axios will catch the 400 status and throw an error in the console. If you customize the status like
res.json({status: 'error'});
You need to handle that differently in your front end. You can do something like
console.log(res.data.status === 'error');

return res.json({status: 'error', message: 'Invalid password'})
It returns status 200, which is not an error (axios know nothing about your status: "error" thing.
status 11000 does not sound right to me. Normally it is something below 600. docs. In your case, I would use 422.
So, for all your errors add status(422) and check if it works.

Related

Throwing Errors or Returning JSON When Registering User in ExpressJS Node

I am implementing a JSON API in Node ExpressJS and wondering if the best practice is to throw errors or return JSON to the caller indicating that something bad has happened.
This is my current implementation. If the username is already registered, I simply return back a message to the user. Should I be throwing an error instead etc?
const register = async (req, res) => {
const { username, password } = req.body
if (username.length == 0 || password.length == 0) {
// SHOULD I THROW AN ERROR
res.json({ success: false, message: 'Username or password is missing.' })
return
}
// check username is already registered
const existingUser = await models.User.findOne({
where: {
username: username
}
})
if (existingUser) {
// SHOULD I THROW AN ERROR
res.json({ success: false, message: 'Username already exists.' })
return
}

Error object in the client side not showing the error message

I am a begginer in developing full stack applications. I'm currently building a React, Node and Express.js application where users login. I have error checking in the backend side and I want to show it on the client side.
here's my Node Code:
app.post("/api/login",async(req,res)=>{
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(404).send({ message: "No user with that email" });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res.status(401).send({ message: "Passwords do not match" });
}
res.send(user);
} catch (error) {
res.status(500).send(error);
}
})
I'm using redux actions to make the request
here's my React Code:
export const logIn = credentials => async dispatch => {
try {
const res = await axios.post("/api/login", credentials);
console.log(res.data);
dispatch({
type: LOG_IN,
payload: res.data
});
} catch (error) {
dispatch({ type: LOG_IN_FAILED });
console.log(error);
}
};
When I console.log the error.message, I'm getting Request failed with status code 404 Instead of the error message e.g { message: "No user with that email" }.But when I make the request with postman, I'm getting the error message { message: "No user with that email" }.Is there a way I can show that on my client side? Because error.message doesnot seem to work
I found the error object on the error.response.data which contained the actual error from my backend which was { message: "No user with that email" } so the code would look like this:
export const logIn = credentials => async dispatch => {
try {
//make request to backend (with axios in my case)
} catch ({reponse}) {
dispatch({ type: LOG_IN_FAILED });
console.log(response.data); //find the error object from the backend incase of an error
}
};
For more info, check out https://github.com/axios/axios/issues/960

Unable to return a thrown error to calling API

I have an API that simply logs in a user. I am testing out certain test cases for when the username or password are invalid. For some reason, I can't detect the thrown error is not being returned to the API.
The API looks like this:
// routes/users.js
router.post('/users/login', async (req, res) => {
//Login a registered user
try {
const { email, password } = req.body
const user = await User.findByCredentials(email, password)
if (!user) {
return res.status(401).send({ error: 'Login failed! Check authentication credentials' })
}
const token = await user.generateAuthToken()
res.send({ user, token })
} catch (error) {
res.status(400).send(error)
}
})
And here is the method in the model that should return the error. Using, the debugger I can step through the code and it looks like all the statements are being executed but the error is returned as an empty object as such, Error: [object Object]
// models/user.models.js
userSchema.statics.findByCredentials = async (email, password) => {
// Search for a user by email and password.
const user = await User.findOne({ email} )
if (!user) {
throw new Error({ error: 'Invalid user name' })
}
const isPasswordMatch = await bcrypt.compare(password, user.password)
if (!isPasswordMatch) {
throw new Error({ error: 'Invalid password' })
}
return user
}
Looking at the code, I don't think (!user) should be considered an error, as the query just simply found no user record that matches the query condition. As far as handling the real error, try below:
If you want to test this, try:
User.findOne({email}, function(err, user) {
if (err) {
//true error
} else {
if (!user) {
//this is when no user matching the email was found
} else {
//user matching email was found
}
}
}
Because there was no runtime error, there would be no error object in the case the no user was found:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
So this seems to do the trick. Not sure why this works and the original approach did not.
The difference is basically a user defined function handling the error message.
userSchema.statics.findByCredentials = async (email, password) => {
// Search for a user by email and password.
const user = await User.findOne({ email })
function myError(message) {
this.message = message
}
myError.prototype = new Error()
if (!user) {
throw new myError('Username provided is incorrect, please try again')
}
const isPasswordMatch = await bcrypt.compare(password, user.password)
if (!isPasswordMatch) {
throw new myError('Password provided is incorrect, please try again')
}
return user
}

Node js Error Handling it crash the server

Error handling in NODE JS
this is my Login Function
router.post('/admin/login', function (req, res) {
User.findOne({ username: req.body.username }, function (err, user) {
if (err) return res.status(500).send('Error on the server.');
if (!user) return res.status(404).send('No user found.');
var passwordIsValid = bcrypt.compareSync(req.body.password, user.password);
if (!passwordIsValid) return res.status(401).send({ auth: false, token: null });
if (req.body.username && req.body.password) {
console.log("enter");
var token = jwt.sign({ id: user._id }, config.secret, {
expiresIn: 86400 // expires in 24 hours
});
res.status(200).send({ auth: true, token: token });
} else {
return res.status(500).send('Error on the server.');
//res.status(500).send("Check Username & Password");
}
});
});
if i forget to enter password the server will be crashed how to handle on this
You need to check to see if the password is being passed before you pass it into the compareSync function.
if (!req.body.password) {
// handle error
return res.status(401).send("Missing or invalid password")
}
If you're doing this you should also check if req.body.username is being provided in the post request. Alternatively for easier method, you can have a try catch wrapped around the query to handle other unexpected errors.
It's better to check in front-end (client side) and also to check for email and password in back-end , there is various libraries to do that , for example i use express-validator lib here is a simple check and of course you can read full docs https://express-validator.github.io/docs/
code sample :
const { check, validationResult, body } = require('express-validator/check');
router.post('/admin/login',[check('email').isEmail(),check('password').isLength({ min: 5 })], (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({ errors: errors.array() });
}
// the rest of your sing in code here
})
and you can check for name length also this is safer for your back-end always check for validation in front-end and back-end, never trust client side only , do your own validation in the back-end , i hope this answer your question
I was having the same issue with a very similar code.
I replaced this line:
(...)
if (!user) return res.status(404).send('No user found.');
For this:
User.findOne({username : req.body.username},function(err, user){
if(err){
return res.status(401).json({message : err})
};
/* Trying to repair error when username is not on DB */
if(user === null) {
return res.status(401).json({auth : false, token : null, message : "Not Authorised User"});
};
/* Replacement worked */
var isPasswordValid = bcrypt.compareSync(req.body.password, user.password);
Hope it works for you.

how to chain statements together using promises

I'm new to node, and learning all about promises, and pg-promise specifically. This is what I want to do using Express and pg-promise:
check email,
if not found check username,
if not found create a new user.
return user id
I've got my repo set up (db.users) which does the sql which is working great.
In my authorization handler I'm not sure how to make the repo calls follow one another. The syntax seems clunky to me. Here's what I have so far:
exports.signup = function( req, res, next ) {
const username = req.body.username;
const email = req.body.email;
const password = req.body.password;
// See if a user with the given email exists
db.users.byEmail({email: email})
.then(user => {
if (user) {
return res.status(422).send({ error: 'Email is in use'});
} else {
return null; <-- must I return something here?
}
})
.then(() => {
db.users.getUsername({username: username})
.then(user => {
if (user) {
return res.status(422).send({ error: 'Email is in use'});
} else {
return null; <-- must I return something here?
}
...etc
})
Maybe pg-promises don't chain together like this? Should they be nested within one another or maybe be completely separate blocks? Also, not quite sure where the catch goes. I've tried every way I can think of, following various tutorials, but I get errors like 'can't set headers that already set' and 'promises aren't being returned'. If any kind person can guide me here, I'd really appreciate it.
You must use a task when executing multiple queries, so they can share one connection, or else the connection management will suffer from performance issues.
db.task(t => {
return t.users.byEmail({email})
.then(user => {
return user || t.users.getUsername({username});
});
})
.then(user => {
if (user) {
res.status(422).send({error: 'Email is in use'});
} else {
// do something else
}
})
.catch(error => {
// process the error
});
It will work well, presuming that your repositories were set up as shown in pg-promise-demo.
The previous 2 answers were really bad advise, and completely against what pg-promise says you should do. See Tasks versus root/direct queries.
And when doing multiple changes, you would usually use a transaction - use tx instead of task.
With guidance from vitaly-t I altered his answer to include separate error messages depending on which thing failed. I also moved up the next "then" into the transaction block to use the "t" of the transaction for the next step which creates user. Much thanks for vitaly-t!
db.task(t => {
return t.users.byEmail({email})
.then(user => {
if (user) {
throw new Error('Email is in use');
} else {
return t.users.byUsername({username});
}
})
.then((user) => {
if (user) {
throw new Error('Username is taken');
} else {
return t.users.addNew({username, email, password});
}
})
})
.then(user => {
res.json({token: tokenForUser(user), username: user.username, aCheck: user.is_admin});
})
.catch(error => {
res.status(422).json({ 'error': error.message});
});
Checking username and email existence is independent of each other. I think Promise.all() will suit your need more than sequentially chaining promise.
exports.signup = function (req, res, next) {
const username = req.body.username;
const email = req.body.email;
const password = req.body.password;
Promise.all([
db.users.byEmail({
email: email
}),
db.users.getUsername({
username: username
})
]).then((results)=> {
if (results[0] || results[1]) {
return res.status(422).send({
error: 'Email is in use' // same error msg as per your snippet
});
}
// here, code for creating a new user
});
};

Resources