How $or operator of MongoDB can make your head scratch

Abhay SinghAbhay Singh
3 min read

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 !' });
    }

Happy Ending

1
Subscribe to my newsletter

Read articles from Abhay Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Abhay Singh
Abhay Singh

I am Abhay Kumar, a skilled Backend Engineer passionate about JavaScript, TypeScript, Node.js, and solving complex problems.