mongodbmongooseadvanced-search

Is it possible to use a variable object inside collection.find() in mongoose?


Context: I am trying to code the back end of an "Advanced Search" option It is a project I'm coding to practice JS, MongoDB(Mongoose), Next, Node, Etc.

The Schema I'm using is the following:

House Schema:{
 name:String,
 address:{
   street:String
   city:String,
   state:String
   },
 operations:{
   rent:Boolean, 
   shortRent:Boolean, 
   purchase:Boolean,
   agentRentPrice:Number,
   agentShortRentPrice:Number,
   agentSellingPrice:Number},
 features:{
   bathrooms:Number,
   bedrooms:Number,
   beds:Number,
   amenities:[String],
   },
}

Now, the FrontEnd sends the following info (in req.body):

query = {
  name: null,
  address: { city: null, state: null },
  operations: {
    rentAvailable: false,
    purchaseAvailable: false,
    shortRentAvailable: false,
    agentRentPriceMin: null,
    agentSellingPriceMin: null,
    agentShortRentPriceMin: null,
    agentRentPriceMax: null,
    agentSellingPriceMax: null,
    agentShortRentPriceMax: null,
  },
  features: {
    bathroomsMin: null,
    dormsMin: null,
    bedsMin: null,
    amenities: null,
  },
}

Of course, those "null" values will be replaced with numbers or strings with the parameters introduced by the user.

With this object, I then declare another:

let queryObj = {
  name: query.name,
  address: { city: query.address.city, state: query.address.state },
  operations: {
    rentAvailable: query.operations.rentAvailable,
    purchaseAvailable: query.operations.purchaseAvailable,
    shortRentAvailable: query.operations.shortRentAvailable,
    agentRentPrice: {
      $gte: query.operations.agentRentPriceMin,
      $lte: query.operations.agentRentPriceMax,
    },
    agentSellingPrice: {
      $gte: query.operations.agentSellingPriceMin,
      $lte: query.operations.agentSellingPriceMax,
    },
    agentShortRentPrice: {
      $gte: query.operations.agentShortRentPriceMin,
      $lte: query.operations.agentShortRentPriceMax,
    },
  },
  features: {
    bathrooms: { $gte: query.features.bathroomsMin },
    dorms: { $gte: query.features.dormsMin },
    beds: { $gte: query.features.bedsMin },
    amenities: { $in: query.features.amenities },
  },
}

Finally I reduce this object, removing any "null", "false", "" and "{}" values.

For example, the user searches for: house available for renting, in New York, with 2 bedrooms, with a pool, and max Rent price of $10.000

So, req.body.query will be

{
  name: null,
  address: { city: "New York", state: null },
  operations: {
    rentAvailable: true,
    purchaseAvailable: false,
    shortRentAvailable: false,
    agentRentPriceMin: null,
    agentSellingPriceMin: null,
    agentShortRentPriceMin: null,
    agentRentPriceMax: 10000,
    agentSellingPriceMax: null,
    agentShortRentPriceMax: null,
  },
  features: {
    bathroomsMin: null,
    dormsMin: 2,
    bedsMin: null,
    amenities: ["pool"],
  },
}

Next, I declare

let queryObj = {
  name: null,
  address: { city: "New York", state: null },
  operations: {
    rentAvailable: true,
    purchaseAvailable: false,
    shortRentAvailable: false,
    agentRentPrice: {
      $gte: null,
      $lte: 10000,
    },
    agentSellingPrice: {
      $gte: null,
      $lte: null,
    },
    agentShortRentPrice: {
      $gte: null,
      $lte: null,
    },
  },
  features: {
    bathrooms: { $gte: null },
    dorms: { $gte: 2 },
    beds: { $gte: null },
    amenities: { $in: ["pool"] },
  },
};

I have a function here that reduces this object (removing "false" "null" "" and "{}" values):


queryObj = {
  address: { city: "New York" },
  operations: {
    rentAvailable: true,
    agentRentPrice: {
      $lte: 10000,
    },
  },
  features: {
    dorms: { $gte: 2 },
    amenities: { $in: ["pool"] },
  },
};

As you can see, "query" (and therefore "queryObj") will vary a lot; the user may or may not use any of the available search parameters, so (as i see it) it is not possible to "hard-code" the queryObj structure

I've tried using

Home.aggregate([{$match:queryObj}]) 

without success (returns no results).

Is it possible what I'm trying to do?


Solution

  • Try to use a function to build the filter:

    const body = {
      name: null,
      address: { city: 'New York', state: null },
      operations: {
        rentAvailable: true,
        purchaseAvailable: false,
        shortRentAvailable: false,
        agentRentPriceMin: null,
        agentSellingPriceMin: null,
        agentShortRentPriceMin: null,
        agentRentPriceMax: 10000,
        agentSellingPriceMax: null,
        agentShortRentPriceMax: null,
      },
      features: {
        bathroomsMin: null,
        dormsMin: 2,
        bedsMin: null,
        amenities: ['pool'],
      },
    };
    
    const buildFilter = (body) => {
      const filter = {};
      if (body.name != null) {
        filter['name'] = body.name;
      }
      if (body.address.city != null) {
        filter['address.city'] = body.address.city;
      }
      if (body.address.state != null) {
        filter['address.state'] = body.address.state;
      }
      if (body.operations.rentAvailable != null) {
        filter['operations.rentAvailable'] = body.operations.rentAvailable;
      }
      if (body.operations.purchaseAvailable != null) {
        filter['operations.purchaseAvailable'] = body.operations.purchaseAvailable;
      }
      if (body.operations.shortRentAvailable != null) {
        filter['operations.shortRentAvailable'] =
          body.operations.shortRentAvailable;
      }
      let agentRentPrice = {};
      if (body.operations.agentRentPriceMin != null) {
        agentRentPrice = { $gte: body.operations.agentRentPriceMin };
      }
      if (body.operations.agentRentPriceMax != null) {
        agentRentPrice = {
          ...agentRentPrice,
          $lte: body.operations.agentRentPriceMax,
        };
      }
      if (Object.keys(agentRentPrice).length > 0) {
        filter['operations.agentRentPrice'] = agentRentPrice;
      }
      let agentSellingPrice = {};
      if (body.operations.agentSellingPriceMin != null) {
        agentSellingPrice = { $gte: body.operations.agentSellingPriceMin };
      }
      if (body.operations.agentSellingPriceMax != null) {
        agentSellingPrice = {
          ...agentSellingPrice,
          $lte: body.operations.agentSellingPriceMax,
        };
      }
      if (Object.keys(agentSellingPrice).length > 0) {
        filter['operations.agentSellingPrice'] = agentSellingPrice;
      }
      let agentShortRentPrice = {};
      if (body.operations.agentShortRentPriceMin != null) {
        agentShortRentPrice = { $gte: body.operations.agentShortRentPriceMin };
      }
      if (body.operations.agentShortRentPriceMax != null) {
        agentShortRentPrice = {
          ...agentShortRentPrice,
          $lte: body.operations.agentShortRentPriceMax,
        };
      }
      if (Object.keys(agentShortRentPrice).length > 0) {
        filter['operations.agentShortRentPrice'] = agentShortRentPrice;
      }
      if (body.features.bathrooms != null) {
        filter['features.bathrooms'] = { $gte: body.features.bathrooms };
      }
      if (body.features.dorms != null) {
        filter['features.dorms'] = { $gte: body.features.dorms };
      }
      if (body.features.beds != null) {
        filter['features.beds'] = { $gte: body.features.beds };
      }
      if (body.features.amenities != null) {
        filter['features.amenities'] = { $in: body.features.amenities };
      }
      return filter;
    };
    
    console.log(buildFilter(body));

    A little verbose, but it is due to the fact that you don't have a 1-on-1 correspondence between the attributes of the body and your filter's properties.