0

I have the following schema:

const userSchema = new mongoose.Schema({
  // long long schema, email, phoneNum ... etc
  name: String,
  password_createTime: {
    type: Date,
    select: false
  }
})

And inside the first middleware in my Express app, I have the following code:

const userDoc = await User.findById(THE_USER_ID).select('+password_createTime')
if (userDoc.password_createTime > someDate) return next(new Error('the password got changed, your token is expired'))
// userDoc.password_createTime = undefined  bad practice 
req.loggedInUser = userDoc

Now I am in the 30nth middleware in the stack, and I finally want to send the response, but when I simply write

res.status(200).json({ data: req.loggedInUser })

It sends the response with the password_createTime field, which should not be visible because this field must be hidden (because of the context of select: false property in the schema).

I have many other routes on the API, that depend on this req.loggedInUser property, which is getting set by the first middleware in the stack. The problem is that some routes on the API modify the req.loggedInUser to change other fields, for instance, the [PATCH /users/me] route is used to update the profile of the user such as the name field, then it uses the .save() method to save the new document. which means that I can't set userDoc.password_createTime = undefined because it's a bad practice, it will be sent to the database as undefined, just because I wanted to remove it from the response.

Now. The question is: I want to remove the password_createTime from all the responses from anywhere in my app without having to worry about it.

How can I do that?

Is there something in mongoose like:

userDoc.unselect('password_createTime')

where I can use it in my code of my first middleware like:

const userDoc = await User.findById(THE_USER_ID).select('+password_createTime')
if (userDoc.password_createTime > someDate) return next(new Error('the password got changed, your token is expired'))
userDoc.unselect('password_createTime') //  ✅ Good practice
req.loggedInUser = userDoc

??

By The way, I was reading this, but I believe that most of the answers here are "bad practices":

Normal
  • 1,616
  • 15
  • 39

1 Answers1

0

You can add a custom toJSON transform:

userSchema.set('toJSON', {
  transform(doc, ret, opts) {
    delete ret.password_createTime;
    return ret;
  }
});

EDIT: say you have some situations where you do want password_createTime to be present in a response. In that case, you can use a workaround:

const obj = req.loggedInUser.toObject();
res.status(200).json({ data: obj })

That's because toJSON (and its transforms) is only called when you JSON-stringify a Mongoose document directly. When you use toObject the transforms for toJSON aren't applied (but you can also define transforms for toObject as well).

robertklep
  • 198,204
  • 35
  • 394
  • 381
  • Ah! How did I miss that! – Normal Jul 03 '22 at 16:40
  • Okay, and say for example I wanted later to send a response with the `password_createTime`, the transform JSON is going to prevent that. In this case, I will have to create another schema with another model? – Normal Jul 03 '22 at 16:45