Add Dynamic Numeric Filters To Your Crud API.

Learn How to Filter Items by Price and Rating.

Hello coders, I hope you are doing fantastic. I've been building a simple REST API which queries the Mongo database for some products. The user should be able to sort the products by name, price and company. The user should also be able to filter the products by name, price and company. However, I wanted to add numeric filters to my API such that the user can query the products whose price or rating is either equal to, greater than, less than, greater than or equal to or less than or equal to a specific value.

For a few hours, its been a pain trying to come up with the logic. However, I came up with a solution and I'm going to share it with you.

I'm not going to go into the details of creating schemas using mongoose and connecting to the database because my main focus is to show you the logic for numeric filters. To access a resource, of course we have to use a controller function which handles all the logic of how we will access the resource in that route. Below is the whole logic. An explanation follows below it.

const getAllProducts= async (req, res, next)=>{

try{
 const {numericFilters}=req.query;
  const queryObject = {};
if (numericFilters) {
    const operatorMap = {
      '>': '$gt',
      '>=': '$gte',
      '=': '$eq',
      '<': '$lt',
      '<=': '$lte',
    };
    const regEx = /\b(<|>|>=|=|<|<=)\b/g;
    let filters = numericFilters.replace(
      regEx,
      (match) => `-${operatorMap[match]}-`
    );
    const options = ['price', 'rating'];
    filters = filters.split(',').forEach((item) => {
      const [field, operator, value] = item.split('-');
      if (options.includes(field)) {
        queryObject[field] = { [operator]: Number(value) };
      }
    });
  }
 const products = model.find(queryObject);
res.status(200).json(products);
}
catch(error){
console.log(error)
res.status(500).send("internal server error")
}
}

To perform numeric filtering, mongoose provides us with the following operators:

✔. $gt

this one corresponds to '>' (greater than)

✔. $gte

this one corresponds to '>=' (greater than or equal to)

✔. $eq

this one corresponds to '=' (equal to)

✔. $lt

this one corresponds to '<' (less than)

✔. $lte

this one corresponds to '<=' (less than or equal to)

To apply numeric filters, a user has to provide something like price >500, rating >=4 the string will be stored in req.query.numericFilters

I created an object whose keys are the symbols these operators represent and the values are these operators.

 const operatorMap = {
      '>': '$gt',
      '>=': '$gte',
      '=': '$eq',
      '<': '$lt',
      '<=': '$lte',
    };

Then I wrote a regular expression which will basically scan for >, >=, =, < or <= in req.query.numericFilters

let filters = numericFilters.replace(
      regEx,
      (match) => `-${operatorMap[match]}-`
    );

The code above will replace the symbols >, >=, <, <= or = with their corresponding operators.

Lets assume a user provided price >500, rating >=4.

That string will be replaced by price-$gt-500, rating-$gte-4.

const options = ['price', 'rating'];

The code above defines the 2 options a user can use to filter the products.

 filters = filters.split(',').forEach((item) => {
      const [field, operator, value] = item.split('-');
      if (options.includes(field)) {
        queryObject[field] = { [operator]: Number(value) };
      }

When we split price-$gt-500, rating-$gte-4 by where there is a comma as shown above, we get an array, [price-$gt-500, rating-$gte-4].

We split each item of the array by '-'.

For the first item we will get [price, $gt, 500] The array is then destructed as shown below

const[field, operator,value]= [price, $gt, 500]

We then check if the options array , const options = ['price', 'rating'] includes the field which in our case is price.

 if (options.includes(field)) {
        queryObject[field] = { [operator]: Number(value) };
      }

If the condition is true, we add a new property to the query object for numeric filtering. In our case it will look something like,

queryObject={
price:{
$gt:500
}
}

When we pass this object to our model i.e model.find(queryObject), we will get back only products whose price is greater than 500. Now you get the gist. The same will work for rating too.

To know more about numeric filtering in mongoose, I recommend reading their DOCS.

I hope you enjoyed🙂.

Â