How to catch mongoose errors when updating a document - node.js

In my Node.js backend, I have an endpoint where user profile info is updated. I want to send user an error response if the new email is already in the database. However, though I have set up a try-catch in place, I still can't catch the error. Instead, the server just crashes with the following mongoose error message. I receive an error response on the front end, but after a very long time from when the error happened. All helpful advice is highly appreciated.
(node:11864) UnhandledPromiseRejectionWarning: MongoError: E11000
duplicate key error collection: testing.users index: email_1 dup key:
{ : "test#gmail.com" } ... (node:11864)
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This
error originated either by throwing inside of an async function
without a catch block, or by rejecting a promise which was not handled
with .catch(). To terminate the node process on unhandled promise
rejection, use the CLI flag --unhandled-rejections=strict (see
https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode).
(rejection id: 1)
UpdateProfile in UserController.js
updateProfile: (id,fname,lname,email,mobile,address,next) => {
let args = {fname,lname,email,mobile,address}
try{
User.findOneAndUpdate({ "_id": id },
{ "$set": Utils.removeNullsFromObject(args)},
{new: true, useFindAndModify: false}).then(function(updatedUser, err){
if(err) {
console.log(err);
next(err,null)
} else {
next(null,updatedUser)
}
);
}
catch(err){
console.log(err);
next(err,null)
}
}

Try...catch will work with async/await and not with promise...then. promise...then has special block called catch which can be used as,
updateProfile: (id,fname,lname,email,mobile,address,next) => {
let args = {fname,lname,email,mobile,address}
User.findOneAndUpdate({ "_id": id },
{ "$set": Utils.removeNullsFromObject(args)},
{
new: true, useFindAndModify: false
}).then(updatedUser => {
next(null,updatedUser)
).catch(err =>{
console.log(err);
next(err,null)
})
}
and if you want to use async/await, then,
updateProfile: async (id,fname,lname,email,mobile,address,next) => {
let args = {fname,lname,email,mobile,address}
try{
const updatedUser = await User.findOneAndUpdate({ "_id": id },
{ "$set": Utils.removeNullsFromObject(args)},
{
new: true, useFindAndModify: false
})
next(null,updatedUser)
} catch(err) {
console.log(err);
next(err,null)
})
}
For more details, you can refer https://javascript.info/promise-error-handling

Related

Mongoose Error when using findByIdAndUpdate

I'm getting Query was already executed error when using findByIdAndUpdate in mongoose to update a todo list.
Console
/Users/ariful/dev/todo/node_modules/mongoose/lib/helpers/query/wrapThunk.js:21
const err = new MongooseError('Query was already executed: ' + str);
^
MongooseError: Query was already executed: Todo.findOneAndUpdate({ _id: new ObjectId("62eb7849edd528678...
at model.Query._wrappedThunk [as _findOneAndUpdate] (/Users/ariful/dev/learn/todo/node_modules/mongoose/lib/helpers/query/wrapThunk.js:21:19)
at /Users/ariful/dev/learn/todo/node_modules/kareem/index.js:426:25
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
originalStack: 'Error\n' +
' at model.Query._wrappedThunk [as _findOneAndUpdate] (/Users/ariful/dev/learn/todo/node_modules/mongoose/lib/helpers/query/wrapThunk.js:25:28)\n' +
' at /Users/ariful/dev/learn/todo/node_modules/kareem/index.js:426:25\n' +
' at process.processTicksAndRejections (node:internal/process/task_queues:77:11)'
}
My Code:
Todo Controller
// PUT TODO
router.put('/:id', async (req, res) => {
// Update a todo
const result = await Todo.findByIdAndUpdate(
{ _id: req.params.id },
{
$set: {
status: "active",
},
},
{
new: true, // for getting updated result
},
(err) => {
if (err) {
res.status(500).json({
error: "Opps! - can't create todo some error happend!"
})
} else {
res.status(200).json({
message: "Todo updated sucessful!"
});
}
}
)
console.log(result);
})
After executing put request the server crashes but the value updated in the database.
You are using async await style, so put the code inside try/catch block.
This should work without an error
try {
const result = await Todo.findByIdAndUpdate(
{ _id: req.params.id },
{
$set: {
status: "active",
},
})
res.status(200).json({ message: "Todo updated sucessful!"});
} catch (error) {
res.status(500).json({
error: "Opps! - can't create todo some error happend!"
})
}

NODE / UPDATE / API / SEQUELIZE: UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'email' of null

EDIT:
The problem was precisely in the authentication (in fact it was working properly, however, the token was referring to the old user's id in the database):
const { email, oldPassword } = req.body;
const user = await User.findByPk(req.userId);
if (email !== user.email) {
const userExists = await User.findOne({ where: { email } });
if (userExists) {
return res.status(400).send({ error: 'User already exists.' });
}
}
Probably, after I manually made changes to the database, the token did not recognize this. (I also put in a 7-day expiration)
EDIT /\
error:
Executing (default): SELECT "id", "name", "email", "password_hash", "provider", "created_at" AS "createdAt", "updated_at" AS "updatedAt" FROM "users" AS "User" WHERE "User"."id" = 7;
(node:1616) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'email' of null
at update (C:\Project\src\app\controllers\UserController.js:56:24)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
(Use `node --trace-warnings ...` to show where the warning was created)
(node:1616) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate
the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:1616) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
P.S:
WHERE "User"."id" = 7;
(the ID in the user's database would be = 1, in this case)
This line represents:
at update (C:\Project\src\app\controllers\UserController.js:56:24)
This part of the code on UserController:
if (email !== user.email) { // <<<---- Here
const userExists = await User.findOne({ where: { email } });
if (userExists) {
return res.status(400).send({ error: 'User already exists.' });
}
}
Understanding the problem:
I am making an API in Node.js, where I am trying to do a user update
I am using sequelize (PostgreSQL) in the database using Docker (myself as a begginer, I thought about the possibility that it was a problem with Docker/Database/Sequelize, because I created 10 fake users and inserted it in the database, after that, I removed all (manually, on Postbird) and of course the first new user_ID would become, 11, for example). Then, I created a new database and the user was created normally with id1.
I considered that the user could only update his information if he was authenticated in:
routes.use(authMiddleware); (in ./src/routes.js)
I didn't put the authentication I did here, I believe it's working 100%.
Here is the project's architecture:
./src/routes.js
import { Router } from 'express';
import UserController from './app/controllers/UserController';
import authMiddleware from './app/middlwares/auth';
const routes = new Router();
routes.use(authMiddleware);
routes.put('/users', UserController.update);
export default routes;
.src/app/controllers/UserController.js
import User from '../models/User';
class UserController {
async update(req, res) {
const { email, oldPassword } = req.body;
const user = await User.findByPk(req.userId);
if (email !== user.email) {
const userExists = await User.findOne({ where: { email } });
if (userExists) {
return res.status(400).send({ error: 'User already exists.' });
}
}
if (oldPassword && !(await user.checkPassword(oldPassword))) {
return res.status(401).json({ error: 'Password does not match' });
}
const { id, name, provider } = await user.update(req.body);
return res.json({
id,
name,
email,
provider,
});
}
}
export default new UserController();
./src/app/models/User.js
import Sequelize, { Model } from 'sequelize';
import bcrypt from 'bcryptjs';
class User extends Model {
static init(sequelize) {
super.init(
{
name: Sequelize.STRING,
email: Sequelize.STRING,
password: Sequelize.VIRTUAL,
password_hash: Sequelize.STRING,
provider: Sequelize.BOOLEAN,
},
{
sequelize,
}
);
this.addHook('beforeSave', async (user) => {
if (user.password) {
user.password_hash = await bcrypt.hash(user.password, 8);
}
});
return this;
}
checkPassword(password) {
return bcrypt.compare(password, this.password_hash);
}
}
export default User;
./src/database/migrations/number-create-users.js
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.createTable('users', {
id: {
type: Sequelize.INTEGER,
allowNulll: false,
autoIncrement: true,
primaryKey: true,
},
name: {
type: Sequelize.STRING,
allowNulll: false,
},
email: {
type: Sequelize.STRING,
allowNulll: false,
unique: true,
},
password_hash: {
type: Sequelize.STRING,
allowNulll: false,
},
provider: {
type: Sequelize.BOOLEAN,
defaultValue: false,
allowNulll: false,
},
created_at: {
type: Sequelize.DATE,
allowNulll: false,
},
updated_at: {
type: Sequelize.DATE,
allowNulll: false,
},
});
},
down: async (queryInterface) => {
await queryInterface.dropTable('users');
},
};
HTTP / PUT JSON Data:
{
"name": "user000",
"email": "email000#domain.com.br",
"oldPassword": "password000",
"password": "123456",
"confirmPassword": "123456"
}
Consdering that the user is already authenticated.
The problem might be that the user is not found by the below query. therefore user might be undefined. Are you sure that this line is correct?
const user = await User.findByPk(req.userId);
and userId shouldn't be either in the body of the request or in the params? i.e.:
const user = await User.findByPk(req.params.userId);
If the user id is in the params, I think you also need to update your route to:
routes.put('/users/:userId', UserController.update);
Furthermore I think its a good practice to wrap async stuff into a try catch block. something like this:
try {
const user = await User.findByPk(req.params.userId);
} catch (err) {
console.log(err)
}

Express login: UnhandledPromiseRejectionWarning: Unhandled promise rejection

Good day developers im trying to trigger a process of login for users in my app, generating a token for security reasons, but for some situation im receiving this error
(node:11088) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async
function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:11088) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
despite of allowing the user to log, but then the token although is created isn't exposed on the login json i generated for app purposes checked from my postman.
Lets say i do start the process first in my folder of token generator, after install jsonwebtoken package
jsonwebtoken generation folder
const jsonwebToken = require("jsonwebtoken");
const generateToken =async (userId) => {
return new Promise((reject, resolve) => {
const tokenPayload = { userId };
jsonwebToken.sign(
tokenPayload,
process.env.TOKEN_SECRET_WORD,
{
expiresIn: "12h",
},
(error, generatedToken) => {
if (error) {
reject("cant generate token");
} else {
resolve(generatedToken);
}
}
);
});
};
module.exports = { generateToken };
Once the process of generate the token is set , on my controller for the loginUser function , i set this
et User = require("../modelos/UserModel");
const { response } = require("express");
const cryptoPass = require("bcryptjs");
const { generateToken } = require("../jsonwebtoken/jsonWebTokenGenerator");//path to the json generator
const loginUser = async (request, response = response) => {
const { userEmail, userPassword } = request.body;
try {
const userInDb = await User.findOne({ userEmail });
if (!userInDb) {
return response.status(400).json({
ok: false,
message: "Not user Found",
});
}
const passwordValid = await cryptoPass.compareSync(
userPassword,
userInDb.userPassword
);
if (!passwordValid) {
return response.status(400).json({
ok: false,
message: "Error in Password",
});
}
const tokenGenerated = generateToken(userInDb.id);//generating the tooken in the process
//in order to add it to the user logged json
//once the response is ok(200)
response.status(200).json({
ok: true,
message: "User Logged",
tokenGenerated,//no token
});
} catch (error) {
response.status(500).json({
ok: false,
message: "Some error happened in login",
});
}
};
module.exports = {
loginUser,
};
But then despite of loggin the user , the token isn't brought in the response and that error shows up:
Is weird but if i use an await when asigning the user id to the method of generate token , and then i trigger all the process, then the login isn't successful, and console loggin the catch of that error brings me the token in fact:
......
const tokenGenerated =await generateToken(userInDb.id);//adding an await
// console.log(tokenGenerated,"controller token");
// console.log(userInDb);
response.status(200).json({
ok: true,
message: "User Logged",
tokenGenerated,
});
} catch (error) {
console.log(error,"Error");//loggin the error in the catch
response.status(500).json({
ok: false,
message: "Some error happened in login",
});
}
and in postman the not successful login
Would be amazing any help on this . Thanks in advance!!
The signature of the callback in new Promise() within generateToken is wrong
You are using
return new Promise((reject, resolve) => {
...
});
but the correct one is
return new Promise((resolve, reject) => {
...
});
ie, you switched the resolve and reject parameter. Thus when in your method you try to call resolve(generatedToken); you are actually rejecting your promise with an error of the generated token.

(node:3966) UnhandledPromiseRejectionWarning: Error: querySrv ECONNREFUSED

I'm trying to fix an error UnhandledPromiseRejectionWarning: Error: querySrv ECONNREFUSED when I make an axios call to fetch user data from mongoose query without internet connection. I've tried to wrap both the mongoose query User.findOne() and mongoose.connect() with try catch, but the error still remain.
(node:3966) UnhandledPromiseRejectionWarning: Error: querySrv
ECONNREFUSED _mongodb._tcp.cluster1-94jth.mongodb.net [0] at
QueryReqWrap.onresolve [as oncomplete] (dns.js:196:19) [0] (node:3966)
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This
error originated either by throwing inside of an async function
without a catch block, or by rejecting a promise which was not handled
with .catch(). (rejection id: 1) [0] (node:3966) [DEP0018]
DeprecationWarning: Unhandled promise rejections are deprecated. In
the future, promise rejections that are not handled will terminate the
Node.js process with a non-zero exit code.
FRONT-END
client.js
try {
const res = await axios.get('/auth/user?', {
params: {
refreshToken: refreshToken,
userID: userID
}
}
);
console.log(res.data);
} catch (error) {
if(error.toJSON().message === 'Network Error'){
alert('no internet connection');
}
}
BACK-END
auth.routes.js
auth.get(
'/user',
async(req, res)=>{
try {
const {userID, refreshToken, uniqueDeviceID, authTimestamp} = req.query;
const existingUser = await User.findOne({
$and: [
{'user_id': userID},
{'refresh_token': refreshToken}
]
});
res.send(existingUser);
} catch (error) {
console.log(error);
}
}
);
server.js
try {
mongoose.connect(keys.mongoURI, {useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true});
console.log('Database Connected');
} catch (error) {
console.log(error);
}
Wrapping mongoose.connect with try/catch will not work because its no doing async/await.
You should:
mongoose.connect(keys.mongoURI, {useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true})
.then(() => console.log('Database Connected'))
.catch(error=> console.log(error));
try/catch won't work with an asynchronous function without await
so to catch an error from asynchronous function:
try {
await someAsyncFunction();
} catch (e) {
//...
}
or you could use catch method from Promise or even use callbacks.

NodeJS: Unhandled Promise Rejection at the end of a method

At the moment, I'm working on a RESTful-API with express and mongoose and I now have a problem.
First, my method:
public create() : Promise<UserDocument> {
return new Promise((user) => {
User.exists(this.username).then((exists) => {
if (exists) {
throw Errors.mongoose.user_already_exists;
} else {
UserModel.create(this.toIUser()).then((result) => {
user(result);
}).catch(() => {
throw Errors.mongoose.user_create
});
}
}).catch((error) => {
throw error;
})
});
}
I get a unhandled promise rejections when I execute this method. This happens even if I handle the error when I execute the method like this:
User.fromIUser(user).create().then(() => {
return response.status(200).send({
message: "Created",
user
});
}).catch((error) => {
return response.status(500).send({
message: error
});
});
Full stacktrace:
(node:23992) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): User already exists
(node:23992) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
How i can avoid this situation?
Thanks for your help,
felixoi
I found the solution! Just use "resolve, request" for creating a promise.
Here is now my method:
public create() : Promise<any> {
return new Promise((resolve, reject) => {
User.exists(this.username).then((exists) => {
if (exists) {
reject( Errors.mongoose.user_already_exists);
} else {
UserModel.create(this.toIUser()).then((result) => {
resolve(result);
}).catch(() => {
reject(Errors.mongoose.user_create);
});
}
}).catch((error) => {
reject(error);
})
})
}
If you call the method now you can use the catch() method and everything works! Call it like this:
User.fromIUser(user).create().then((user) => {
return response.status(200).send({
message: "Created",
user
});
}).catch((error) => {
return response.status(500).send({
message: error
})
})

Resources