How $or operator of MongoDB can make your head scratch
Hey, what's the actual problem man?
I was developing a user signup functionality for one of my applications in Node.js ❤️. I want any user can sign up either by their email or by their phone number, similar to an e-commerce sign-up feature ( You don't need to remember your password ).
At first, I thought I would check whether the user exists in My Db or not. If It does exist then prevent the user from signing Up and redirect him to log in with their credentials (either by the phone number or by email ).
this is what my previous code looks like.
export async function signUp(req: Request, res: Response) {
try {
const incomingBody: createUserBody = req.body;
// check whether user already exist in db or not either with phone number or with email
const userExist = await Usermodel.findOne({ {
$or: [
{ email: incomingBody.email },
{phone_number: incomingBody.phone_number} // here is the culprit
],
});
if (userExist) {
// throw error
return res
.status(StatusCodes.BAD_REQUEST)
.json({ error: 'user already exist !' });
}
// Continue with the signup flow
// some other checks
} catch (error) {
console.log(error);
return res
.status(StatusCodes.INTERNAL_SERVER_ERROR)
.json({ error: 'Internal Server Error' });
}
}
everything seems perfect right? But here is the catch, what will happen if the user does not pass the phone number or the email, although I have implemented that either of the fields must be passed to proceed. In that case, the field's value will be undefined, and MongoDB is returning me data but why ????, it should return Null.
Let us try to debug it
Before diving deep let us the User schema structure and the db. collection.
User Schema Structure.
import mongoose, { Schema } from 'mongoose';
const userSchema = new Schema(
{
name: {
type: String,
required: false,
},
email: {
type: String,
required: false,
unique: true,
},
phone_number: {
type: String,
required: false,
unique: true,
default: null, // adding this line will not Save you either 😈...
},
gender: {
type: String,
required: false,
},
},
{ timestamps: true },
); // timestamps to add created_at and updated_at
export const Usermodel =
mongoose.models.User || mongoose.model('User', userSchema);
So I defined default: null above so that at least some value should be assigned to phone_number, and when I am fetching the data it should work fine because this time it will give me NULL, which I am expecting it to return. But adding this has not solved the problem, why? will tell you later.
my users.collection
Yes, the true problem was with the falsy values, and the $or operator was working as it was supposed to be.
The query above is searching for a document in the users
collection where either phone_number
is undefined
or email
is equal to 'abhaysinghs772@gmail.com'.
From this data, it seems that the condition { email: '
abhaysinghs772@gmail.com
' }
is not met, but the other condition { phone_number: undefined }
is met, resulting in the document being returned. because either it was null or undefined or 0 or '' (empty string) it will be treated exactly the same because all these are Falsy Values.
mdn docs: https://developer.mozilla.org/en-US/docs/Glossary/Falsy
Quick fix
I also tried adding $ne operator (not equals to operator), but it will still return the result because this time the phone number is !=== undefined and that is true also.
users.findOne({ email: 'abhaysinghs772@gmail.com', phone_number: { $ne: undefined } }, {})
added this line of code to fix.
async function checkUserExist(incomingBody: createUserBody) {
if (incomingBody.email) {
return Usermodel.findOne({ email: incomingBody.email });
} else {
return Usermodel.findOne({
phone_number: incomingBody.phone_number,
});
}
}
// check whether user already exist in db or not either with phone number or with email
const userExist = await checkUserExist(incomingBody);
if (userExist) {
// throw error
return res
.status(StatusCodes.BAD_REQUEST)
.json({ error: 'user already exist !' });
}