Stan Loona
Stan Loona

Reputation: 695

Mongoose/NextJS - Model is not defined / Cannot overwrite model once compiled

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);

Upvotes: 26

Views: 11921

Answers (4)

MEET BHINGRADIYA
MEET BHINGRADIYA

Reputation: 57

export const Users: mongoose.Model<IUser> = mongoose.models?.Users || mongoose.model<IUser>("Users", User_Schema);

Upper Formate of export model is worked for me on December 2024 nextjs version : 15.1.0

for ES6 Import/Export

Upvotes: 0

Coder Gautam YT
Coder Gautam YT

Reputation: 2127

Simplest answer (2023)

What's happening is Next.js likes to keep rebuilding your code causing some issues with the cache in Mongoose (mongoose.models).

Basically what you want to do is instead of creating a new Model every time it rebuilds, check if the model is already loaded in the Mongoose cache, and if it is, just use that instead of remaking it.

So in your code you would replace

mongoose.model('Profile', profileSchema);

with

mongoose.models.Profile ?? mongoose.model('Profile', profileSchema);

In simpler code, what this is doing is basically this:

let Profile;
if(mongoose.models.Profile) Profile = mongoose.models.Profile
else Profile = mongoose.model('Profile', profileSchema);

Upvotes: 3

Trey
Trey

Reputation: 67

For me it was simply adding the last line of Stan Loona's answer:

module.exports = mongoose.models.User || mongoose.model("User", UserSchema);

Upvotes: 6

Stan Loona
Stan Loona

Reputation: 695

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);

Upvotes: 23

Related Questions