TL;DR EDIT: If you're coming from Google, this is the solution:
module.exports = mongoose.models.User || mongoose.model("User", UserSchema);
For the non-TL;DR answer, check accepted answer.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I'm working on a NextJS site which in the backend I'm using Mongoose and Express. Whenever I use signUp function, I get this error on backend:
{"name":"Test","hostname":"DESKTOP-K75DG72","pid":7072,"level":40,"route":"/api/v1/signup","method":"POST","errorMessage":"UserModel is not defined","msg":"","time":"2020-06-17T23:51:34.566Z","v":0}
I suspect this error is because I'm using the UserModel in other controllers. This error wasn't happening until I made a new controller. So my question is, how do I fix this issue/how do I use the same model in different controllers/middlewares?
I think the issue is related to node.js - Cannot overwrite model once compiled Mongoose this post, I was getting this error earlier but somehow managed to fix it.
EDIT: The error lies in models/User.js, in the pre save middleware, UserModel isn't defined at the level, how do I validate whether a user already exists with that username and if so, reject the new document?
At controllers/RegisterLogin.js [where the bug is happening]
const UserModel = require("../models/User");
// More packages...
async function signUp(req, res) {
try {
const value = await signUpSchema.validateAsync(req.body);
const response = await axios({
method: "POST",
url: "https://hcaptcha.com/siteverify",
data: qs.stringify({
response: value.token,
secret: process.env.HCAPTCHA,
}),
headers: {
"content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
});
if (!response.data.success) {
throw new Error(errorHandler.errors.HCAPTCHA_EXPIRED);
}
const hashPassword = await new Promise((res, rej) => {
bcrypt.hash(
value.password,
parseInt(process.env.SALTNUMBER, 10),
function (err, hash) {
if (err) rej(err);
res(hash);
}
);
});
await UserModel.create({
userName: value.username,
userPassword: hashPassword,
userBanned: false,
userType: "regular",
registeredIP: req.ip || "N/A",
lastLoginIP: req.ip || "N/A",
});
return res.status(200).json({
success: true,
details:
"Your user has been created successfully! Redirecting in 6 seconds",
});
} catch (err) {
const { message } = err;
if (errorHandler.isUnknownError(message)) {
logger.warn({
route: "/api/v1/signup",
method: "POST",
errorMessage: message,
});
}
return res.status(200).json({
success: false,
details: errorHandler.parseError(message),
});
}
}
module.exports = { signUp };
At controllers/Profile.js [if I use UserModel here, it breaks everything]
const UserModel = require("../models/User");
//plus other packages...
async function changePassword(req, res) {
try {
const value = await passwordChangeSchema.validateAsync(req.body);
const username = await new Promise((res, rej) => {
jwt.verify(value.token, process.env.PRIVATE_JWT, function (err, decoded) {
if (err) rej(err);
res(decoded.username);
});
});
const userLookup = await UserModel.find({ userName: username });
if (userLookup == null || userLookup.length == 0) {
throw new Error(errorHandler.errors.BAD_TOKEN_PROFILE);
}
const userLookupHash = userLookup[0].userPassword;
try {
// We wrap this inside a try/catch because the rej() doesnt reach block-level
await new Promise((res, rej) => {
bcrypt.compare(value.currentPassword, userLookupHash, function (
err,
result
) {
if (err) {
rej(errorHandler.errors.BAD_CURRENT_PASSWORD);
}
if (result == true) {
res();
} else {
rej(errorHandler.errors.BAD_CURRENT_PASSWORD);
}
});
});
} catch (err) {
throw new Error(err);
}
const hashPassword = await new Promise((res, rej) => {
bcrypt.hash(
value.newPassword,
parseInt(process.env.SALTNUMBER, 10),
function (err, hash) {
if (err) rej(err);
res(hash);
}
);
});
await UserModel.findOneAndUpdate(
{ userName: username },
{ userPassword: hashPassword }
);
return res.status(200).json({
success: true,
details: "Your password has been updated successfully",
});
} catch (err) {
const { message } = err;
if (errorHandler.isUnknownError(message)) {
logger.warn({
route: "/api/v1/changepassword",
method: "POST",
errorMessage: message,
});
}
return res.status(200).json({
success: false,
details: errorHandler.parseError(message),
});
}
}
At models/User.js
const mongoose = require("mongoose");
const errorHandler = require("../helpers/errorHandler");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
userName: String,
userPassword: String,
userBanned: Boolean,
userType: String,
registeredDate: { type: Date, default: Date.now },
registeredIP: String,
lastLoginDate: { type: Date, default: Date.now },
lastLoginIP: String,
});
UserSchema.pre("save", async function () {
const userExists = await UserModel.find({
userName: this.get("userName"),
})
.lean()
.exec();
if (userExists.length > 0) {
throw new Error(errorHandler.errors.REGISTER_USERNAME_EXISTS);
}
});
module.exports = mongoose.model("User", UserSchema);
I've managed to fix it. There were two problems here.
1) "UserModel" variable doesn't exist in the pre middleware. Solved by instantiating this.constructor which apparently solves the issue (will need further testing)
2) There's apparently a issue with NextJS building everything, it seems like it's trying to create a new model whenever I use any function from UserModel. This is fixed exporting the already created model
const mongoose = require("mongoose");
const errorHandler = require("../helpers/errorHandler");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
userName: String,
userPassword: String,
userBanned: Boolean,
userType: String,
registeredDate: { type: Date, default: Date.now },
registeredIP: String,
lastLoginDate: { type: Date, default: Date.now },
lastLoginIP: String,
});
UserSchema.pre("save", async function () {
try {
const User = this.constructor;
const userExists = await User.find({
userName: this.get("userName"),
})
.lean()
.exec();
if (userExists.length > 0) {
throw new Error(errorHandler.errors.REGISTER_USERNAME_EXISTS);
}
} catch (err) {
throw new Error(errorHandler.errors.REGISTER_USERNAME_EXISTS);
}
});
module.exports = mongoose.models.User || mongoose.model("User", UserSchema);
For me it was simply adding the last line of Stan Loona's answer:
module.exports = mongoose.models.User || mongoose.model("User", UserSchema);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With