I'm having an issue with refactoring a function used to create a "post", which then saves it on a "user". It works just fine with the .then() syntax, but I can't seem to figure out how to make this work with async/await.
The post is created, and when I look at the User it is supposed to be saved to, the post id shows up on the User. However, the Post never gets a reference to the User id when created. This is what I have currently.
const create = async (req, res) => {
const userId = req.params.id;
try {
const foundUser = await db.User.findById(userId);
const createdPost = await db.Post.create(req.body);
foundUser.posts.push(createdPost._id);
await foundUser.save((err) => {
if (err) return console.log(err);
});
res.json({ post: createdPost });
} catch (error) {
if (error) console.log(error);
res.json({ Error: "No user found."})
}
}
EDIT: As requested, here is a snippet of my schema for posts.
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const postSchema = new Schema(
{
title: {
type: String,
required: true,
maxlength: 100,
},
description: {
type: String,
maxlength: 300,
},
date: {
type: Date,
default: Date.now(),
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
comments: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Comment",
},
],
},
{ timestamps: true }
);
const Post = mongoose.model("Post", postSchema);
module.exports = Post;
The issue is probably here, you're saving the document, but the await here does nothing since you're passing a callback function, so your code does not wait for the response.
await foundUser.save((err) => {
if (err) return console.log(err);
});
There's no need to catch any errors here either since you're in a try catch, so the correct line of code here would be
await foundUser.save()
So, I decided to take a look back at my way of doing this function while using .then(), and I noticed there was a line that I at first thought was unnecessary. I added req.body.user = userId after finding the User. This then gave me the reference to the User on my Post. So, I tried this with my async-await version and it worked! I'm not sure if this is the "right" way to go about this though.
Below I've included the working code:
const create = async (req, res) => {
const userId = req.params.id;
try {
const foundUser = await db.User.findById(userId);
req.body.user = userId;
const createdPost = await db.Post.create(req.body);
foundUser.posts.push(createdPost._id);
await foundUser.save();
res.json({ post: createdPost });
} catch (error) {
if (error) console.log(error);
res.json({ Error: "No user found."})
}
}
Related
updateProfile: async function(req, res) {
try {
const update = req.body;
const id = req.params.id;
if (!req.files || Object.keys(req.files).length === 0) {
return res.status(400).send('No files were uploaded.');
}
const image = req.files.profileImage;
const cloudFile = await upload(image.tempFilePath);
const profileImage = cloudFile.url
console.log('Loging cloudfile', profileImage)
await User.updateOne(id, { update }, { profileImage }, { new: true },
function(err, doc) {
if (err) {
console.log(err)
}
if (doc) {
return res.status(200).send({ sucess: true, msg: 'Profile updated successful' })
}
});
} catch (error) {
res.status(500).json({ msg: error.message });
}
}
But I'm getting an error of "Callback must be a function, got [object Object]"
I have tried to $set: update and $set: profileImage but still not working.
So the image successful upload into the cloudinary but the update for mongoose is not working.
Upon brief research into the issue, I think you are feeding the arguments in wrong. Objects can be confusing but not to worry.
Your code is:
await User.updateOne(id, { update }, { profileImage }, { new: true }
However, I believe it should be something more like:
await User.updateOne({id: id}, { profileImagine: profileImage, new: true },
The API reference annotates use of the function as:
const filter = { name: 'John Doe' };
const update = { age: 30 };
const oldDocument = await User.updateOne(filter, update);
oldDocument.n; // Number of documents matched
oldDocument.nModified; // Number of documents modified
I'm learning node.js and it's amazing, especially with mongo, but sometimes I struggle to solve a simple problem, like patching only 1 attribute in my user database.
It's easier to patch something that cannot be unique, but I want to patch an username attribute and I defined it as "unique" in my schema. I don't know why, but MongoDB doesn't care other db entry has the same user, it let me save.
My schema:
/** #format */
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema(
{
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
email: { type: String, required: true, unique: true },
userNumber: { type: Number, required: true },
description: { type: String },
verified: { type: Boolean, default: false },
isAdmin: { type: Boolean, default: false },
isSubscriber: { type: Boolean, default: false },
isDisabled: { type: Boolean, default: false },
acceptedTerms: { type: Number, required: true },
},
{ timestamps: true }
);
module.exports = mongoose.model('User', userSchema);
On my user controllers in node, I want to updateOne({ _id: userId}, { username: myNewUsername} but it always happens, it doesn't take into consideration another db entry can have the username, so I tried a different strategy but it doesn't work:
exports.changeUsername = (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
User.findOne({ username: req.body.username })
.then(result => {
console.log(result);
if (result.username) {
const error = new Error('Could not find this sport');
error.code = 'DUPLICATED';
throw error;
}
return;
})
.catch(err => next(err));
// if no username was in use then updateOne
User.updateOne({ _id: userId }, { username: newUsername })
.then(result => {
res.status(200).json({
message: 'username has been updated',
username: result.username,
});
})
.catch(err => next(err));
};
I don't know if I can updateOne at the same time add some find validation. What I am doing wrong? Users cannot have the same username.
On the console, it seems it works, but it throws an extra error I don't understand:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:371:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (/Users/username/Sites/pipi-api/node_modules/express/lib/response.js:776:10)
I tried this other approach and it works, but doesn't trigger an error if the record is not unique as I stated in the schema.
// GET ONLY ONE SPORT BY ID
exports.changeUsername = async (req, res, next) => {
// Requirements
const userId = req.params.userId;
const newUsername = req.body.username;
console.log('userId: ' + userId);
console.log('newUsername: ' + req.body.username);
try {
const oldUsername = await User.findOne({ username: newUsername });
if (oldUsername.username) {
throw new Error('Error: its duplicated');
}
const user = await User.findOneAndUpdate(
{ _id: userId },
{ username: newUsername },
{ new: true }
);
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (err) {
console.log('ERROR: ', err);
return res.status(400).json({ success: false });
}
};
If I uncomment the code above, it triggers an error if I find a record on the database that matches but it doesn't allow me to continue to my next line of codes I the username is not found on the db.
I get a new error:
userId: 6231bdef334afbde85ed9f43
newUsername: tetete
ERROR: TypeError: Cannot read properties of null (reading 'username')
at exports.changeUsername (/Users/user/Sites/pipi-api/v1/controllers/users/index.js:43:21)
That error is not related to Mongo. It means that you are trying to send a response and the response is already sent.
The issue is because you called both User.findOne and User.updateOne and both of them has .then handler. So the first one of these that finishes will send the actual response. In the moment the second one finished, the response is already send and the error is thrown because you are trying to send response again.
Mongo will throw the error if you try to change username property that some other user already have. You should check if the req.params.userId and req.body.username sent correctly to the backend. Try to console.log() them and check if they are maybe null.
Consider refactoring your handler to use async/await instead of then/catch. You can do it like this:
exports.changeUsername = async (req, res, next) => {
try {
const userId = req.params.userId;
const newUsername = req.body.username;
const user = await User.findOneAndUpdate({ _id: userId }, { username: newUsername }, { new: true });
console.log('User successfully updated.');
return res.status(200).json({ success: true, user });
} catch (error) {
console.log('ERROR: ', error);
return res.status(400).json({ success: false });
}
}
I have a code where I am updating my schema object with request body. I have applied validation rules on the schema. The problem is, I want the schema to throw an error when there's a non existing field in the request body. Non existing key doesn't save to the database as I want but I want to throw some error instead of saving the object. Schema:
const peopleSchema = new mongoose.Schema(
{
fullname: {
type: String,
required: [true, "fullname is required"],
validate: [(value) => isAlpha(value, "en-US", {ignore: " "}), "name should be alphabetic only"],
},
phone: {
type: String,
validate: [isPhone, "please enter a valid phone number"],
},
address: String,
},
{ timestamps: true }
);
Code to update person:
router.put("/:id", checkUser, async (req, res, next) => {
try {
const { id } = req.params;
const user = req.currentUser;
const person = user.people.id(id);
person.set(req.body);
const response = await user.save();
res.json({ response });
} catch (err) {
next(new BadRequestError(err));
}
});
for validation there are two way based on callback and async approache ,
because your code is based on async/await you must to use validateSync() like the following code:
let errors = user.validateSync()//check validation
if(errors){
console.log(errors)
throw errors;//handle your error
}
const response = await user.save()
in callback method :
user.save(function(err,response){
if (err){
console.log(err);
//handle error
}
else{
console.log(response)
res.json({ response });
}
})
I am getting error even after saving document to the mongodb using mongoose in node express.js project.
Here's my code:
exports.storeJob = async (req, res, next) => {
const { name, email, password, title, location, descriptionUrl, tags, company, companyLogo, coupon, showLogo, highlightWithColor, customColor, makeSticky } = req.body;
const { error } = userRegisterValidation(req.body);
if (error) return res.status(400).json({ success: false, message: error.details[0].message });
const emailExists = await User.findOne({ email: email });
if (emailExists) return res.status(400).json({ success: false, message: "User already exits. Please Login" });
const salt = await bcrypt.genSalt(10);
const hashPassword = await bcrypt.hash(password, salt);
const user = new User({
name: name,
email: email,
password: hashPassword
});
// try{
const savedUser = await user.save();
const job = new Job({
title,
location,
descriptionUrl,
tags,
company,
companyLogo,
coupon,
showLogo,
highlightWithColor,
customColor,
makeSticky,
status: 'open',
user: savedUser
});
try {
const createdJob = await job.save();
// try {
user.jobs.push(createdJob);
user.save();
res.status(201).json({ success: true, data: savedUser });
// } catch {
// res.status(400).json({ success: false, message: "Some error occured" });
// }
} catch (err) {
res.status(400).json({ success: false, message: "Error while creating job.", error: err });
}
// } catch(err) {
// res.status(400).json({ success: false, message: "Error while creating user" });
// }
}
I have 2 questions:
I have register method in userController. Is there any way to use that method inside storeJob method?
In the above code even after saving user and job to the database and linking them api response is
{ success: false, message: "Error while creating job.", error: {} }
user.jobs.push(createdJob);
user.save();
In that case, this two lines creates a new user, because user defines the User schema.
Instead of these two lines try this
var push = {
jobs:createdJob
}
var update = {
"$addToSet":push
}
await User.findOneAndUpdate({ "_id": savedUser._id },update).exec();
Hope it will work fine to you. Thanks
I am trying to run the following script in my Node app to check if any users exist and if not, create first admin user. Yet the script simply do nothing, return nothing even while using Try/Catch so can someone please tell me what I am missing / doing wrong here? or how I can possibly catch the error (if any)? Thanks
import pmongo from 'promised-mongo';
import crypto from 'crypto';
const salt = 'DuCDuUR8yvttLU7Cc4';
const MONGODB_URI = 'mongodb://localhost:27017/mydb';
const db = pmongo(MONGODB_URI, {
authMechanism: 'ScramSHA1'
}, ['users']);
async function firstRunCheckAndCreateSuperAdmin(cb) {
const username = 'admin2#test2.com';
try {
const user = await db.users.findOne({ role: 'admin'});
console.log(user);
if(!user) return cb('No user found');
} catch(e) {
cb('Unexpected error occurred');
}
if(!user) {
console.log('No admin detected.');
const adminPassword = crypto.pbkdf2Sync ( 'password', salt, 10000, 512, 'sha512' ).toString ( 'hex' );
await db.users.update({username: username}, {$set: {username: username, password: adminPassword, role: 'admin'}}, {upsert: true});
}
db.close();
process.exit();
}
firstRunCheckAndCreateSuperAdmin(function(err, resultA){
if(err) console.log(err);
});
You are not returning any callback when there is no admin user in the following code snippet
if (!user) {
console.log('No admin detected.');
const adminPassword = crypto.pbkdf2Sync ( 'password', salt, 10000, 512, 'sha512' ).toString ( 'hex' );
await db.users.update({username: username}, {$set: {username: username, password: adminPassword, role: 'admin'}}, {upsert: true});
// call cb(user) here
}
Please see comment.
import pmongo from 'promised-mongo';
import crypto from 'crypto';
const salt = 'DuCDuUR8yvttLU7Cc4';
const MONGODB_URI = 'mongodb://localhost:27017/mydb';
const db = pmongo(MONGODB_URI, {
authMechanism: 'ScramSHA1'
}, ['users']);
async function firstRunCheckAndCreateSuperAdmin(cb) {
const username = 'admin2#test2.com';
try {
const user = await db.users.findOne({
role: 'admin'
});
console.log(user);
//(1) If user is undefined, then launch cb with an error message;
if (!user) return cb('No user found');
} catch (e) {
//(2) If something is wrong, then launch cb with an error message;
cb('Unexpected error occurred');
}
//This part of the code will only be reached if user is defined.
//This is a dead code as if user is undefined, it would have exited at (1)
if (!user) {
console.log('No admin detected.');
const adminPassword = crypto.pbkdf2Sync('password', salt, 10000, 512, 'sha512').toString('hex');
await db.users.update({
username: username
}, {
$set: {
username: username,
password: adminPassword,
role: 'admin'
}
}, {
upsert: true
});
}
//So if user exists, it will close db and exit without calling cb.
db.close();
process.exit();
}
firstRunCheckAndCreateSuperAdmin(function(err, resultA) {
if (err) console.log(err);
});
Note:
If you are using async/await, then you don't need to use callback.
If you are using callback, then you don't need to have a return statement.
If the intention of the function is suppose to have a return value, make sure all code path returns a value.
I have tried to rewrite your code to make it smaller and to remove all node-style callback types of async code from it. I replaced update with insertOne since you only have one user to insert (not multiple to update). Also I have added 500 ms timeout when calling firstRunCheckAndCreateSuperAdmin in case it "hangs". It should log something at the end :)
import pmongo from 'promised-mongo'
import crypto from 'crypto'
import {
promisify
} from 'util'
const pbkdf2 = promisify(crypto.pbkdf2)
const salt = 'DuCDuUR8yvttLU7Cc4'
const MONGODB_URI = 'mongodb://localhost:27017/mydb'
const db = pmongo(MONGODB_URI, {
authMechanism: 'ScramSHA1'
}, ['users']);
const username = 'admin2#test2.com'
async function firstRunCheckAndCreateSuperAdmin() {
let user = await db.users.findOne({
role: 'admin'
});
if (!user) { // no user lets create one
user = await db.users.insertOne({
username: username,
password: (await pbkdf2('password', salt, 10000, 512, 'sha512')).toString('HEX'),
role: 'admin'
});
}
return user
}
const timeout = delay => message => new Promise((_, reject) => setTimeout(reject, delay, new Error(message)))
Promise
.race([firstRunCheckAndCreateSuperAdmin(), timeout(500)('Rejected due to timeout')])
.then(user => console.log(`Got user ${JSON.stringify(user)}`))
.catch(error => console.error(error))