> For the complete documentation index, see [llms.txt](https://kinesis-school-of-programming.gitbook.io/nestjs-unleashed/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://kinesis-school-of-programming.gitbook.io/nestjs-unleashed/extra-module-5-advanced-querying/filtering-and-sorting/filter-operators.md).

# Filter operators

A more robust way to filter when comparing numbers.

In any real-world online shop, customers would not search for products with an exact price, but below a certain price or within a price range, for example. This involves a certain complexity, so we should better explore and discuss this situation and hypothetical scenarios before proceeding further.

When finding products that cost up to $50, we should receive:

* The **value** itself (<mark style="color:blue;">50</mark>)
* The **filter operator** (<mark style="color:blue;">`lte`</mark>, short for **Lower Than or Equal**)
* Ex: <mark style="color:blue;">price=lte:50</mark>

We may then follow the aforementioned design with other **unary filter operators**. They are:

* <mark style="color:blue;">`lt`</mark> - Lower Than
* <mark style="color:blue;">`lte`</mark> - Lower Than or Equal
* <mark style="color:blue;">`gt`</mark> - Greater Than
* <mark style="color:blue;">`gte`</mark> - Greater Than or Equal
* <mark style="color:blue;">`eq`</mark> - Equal

We can notice an extra layer of complexity here, as both the **value** and the **operator** must be sent together, and they should be extracted correctly. But there is more: when finding products that cost between two prices, a **binary filter operator** is needed, as it receives **two operands**.

So, when finding products that cost between $100 and $200, we would need:

* The **first value** (<mark style="color:blue;">100</mark>)
* The **second value** (<mark style="color:blue;">200</mark>)
* The **filter operator** (<mark style="color:blue;">`btw`</mark>, short for **Between**)
* Ex: <mark style="color:blue;">price=btw:100,200</mark>

What we can notice there:

* This data needs to be correctly "sliced", as it will all arrive concatenated as a single query param
* The <mark style="color:blue;">`price`</mark> may assume different forms, being either one or two values
* The <mark style="color:blue;">`operator`</mark> may operate with a varying amount of operands

It seems this step of the journey won't be quite a walk in the park... But don't worry, we'll get through this together. So, let's start with a positive mood and good spirits.

The first step is to create a DTO to represent a **filter operator** and its **operands**. Let's then create it in <mark style="color:purple;">querying</mark>/<mark style="color:purple;">dto</mark>/<mark style="color:purple;">filter-operation.dto</mark>.

```typescript
const Operator = ['gt', 'gte', 'lt', 'lte', 'eq', 'btw'] as const;
type Operator = (typeof Operator)[number];

export class FilterOperationDto {
  @IsIn(Operator)
  readonly operator: Operator;

  @IsNumber({}, { each: true })
  readonly operands: number[];
}
```

Well, so far there's nothing here that deviates from what is already known. As we want this DTO to be reusable, we just enforce the <mark style="color:blue;">`operands`</mark> to be numbers, even though there are no negative prices and the validation then becomes a bit more lenient. This could be improved at another moment.

However, note that the <mark style="color:blue;">`operator`</mark> should determine the <mark style="color:blue;">length</mark> of the <mark style="color:blue;">`operands`</mark> array. Due to this, we'll need to create a new kind of decorator: one whose validation depends on **many fields**, and not just on the one upon which it is placed. Let's then create it in <mark style="color:purple;">querying</mark>/<mark style="color:purple;">decorators</mark>/<mark style="color:purple;">validate-filter-operands-length.decorator</mark>.

You'll also notice that some of this content is nothing new, we created decorators from scratch when improving the password validation. The difference is in the function that contains the validation logic:

* Through the <mark style="color:blue;">`args`</mark> parameter, of type <mark style="color:blue;">`ValidationArguments`</mark>, we can obtain a reference to the <mark style="color:blue;">`object`</mark> being validaded, and then perform a **type assertion** because this will only be used in the <mark style="color:blue;">`FilterOperationDto`</mark> anyway
* Extract the <mark style="color:blue;">`operator`</mark> and <mark style="color:blue;">`operands`</mark>, which we assume to exist due to the assertion
* Then, depending on the <mark style="color:blue;">`operator`</mark>, we check if the amount of <mark style="color:blue;">`operands`</mark> is correct

```typescript
const VALIDATE_FILTER_OPERANDS_LENGTH_KEY = 'validateFilterOperandsLength';

const validateFilterOperandsLength = (args: ValidationArguments) => {
  const filterOperationDto = args.object as FilterOperationDto;
  const { operator, operands } = filterOperationDto;

  switch (operator) {
    case 'lt':
    case 'lte':
    case 'gt':
    case 'gte':
    case 'eq':
      return operands.length === 1;

    case 'btw':
      return operands.length === 2;

    default:
      const exhaustiveCheck: never = operator;
      return exhaustiveCheck;
  }
};

export const ValidateFilterOperandsLength = (): PropertyDecorator =>
  ValidateBy({
    name: VALIDATE_FILTER_OPERANDS_LENGTH_KEY,
    validator: {
      validate: (value, args): boolean => validateFilterOperandsLength(args),
      defaultMessage: () => 'Operands length is not according to filter operator',
    },
  });
```

{% hint style="info" %}
The <mark style="color:blue;">`exhaustiveCheck`</mark> at the end of the <mark style="color:blue;">switch</mark> ensures that no <mark style="color:blue;">case</mark> is overlooked.
{% endhint %}

And remember that the validation of this decorator does not depend on the field upon which it is placed. Due to this, we may create a field just for this purpose. As it will simply receive the decorator and won't be directly used, we may make it **private** and set its type to <mark style="color:yellow;">any</mark>.

```typescript
@ValidateFilterOperandsLength()
private readonly manyFieldsValidation: any;
```

The DTO validation is done. The next step is to create a decorator for a custom **Transform**, in order to correctly separate and obtain the data that will arrive as the <mark style="color:blue;">`price`</mark> query param. Only after this, the validation we just implemented should take place. Let's then create it in <mark style="color:purple;">querying</mark>/<mark style="color:purple;">decorators</mark>/<mark style="color:purple;">to-filter-operation-dto.decorator</mark>.

The steps performed here are:

* The index of the first <mark style="color:blue;">`:`</mark> is obtained with <mark style="color:blue;">`indexOf()`</mark>
* If there is no <mark style="color:blue;">`:`</mark> , we return the <mark style="color:blue;">`operator`</mark> as <mark style="color:blue;">`value`</mark>, and <mark style="color:blue;">`operands`</mark> as <mark style="color:blue;">`[]`</mark>&#x20;
* After that, the <mark style="color:blue;">`operator`</mark> receives what is before this <mark style="color:blue;">`:`</mark> with <mark style="color:blue;">`substring()`</mark>, and <mark style="color:blue;">`concOperands`</mark> receives what is after it
* The <mark style="color:blue;">`operandsStr`</mark> receives the value of <mark style="color:blue;">`concOperands`</mark> split on the <mark style="color:blue;">`,`</mark>
* <mark style="color:blue;">`operands`</mark> receives the values of the <mark style="color:blue;">`operandsStr`</mark> array, cast to number
* The <mark style="color:blue;">`plainDto`</mark> object receives the values of <mark style="color:blue;">`operator`</mark> and <mark style="color:blue;">`operands`</mark>
* An instance of <mark style="color:blue;">`FilterOperationDto`</mark>, generated from the <mark style="color:blue;">`plainDto`</mark>, is returned

```typescript
const toFilterOperationDto = (value: string) => {
  const colonIndex = value.indexOf(':');

  if (colonIndex === -1) {
    const plainDto = { operator: value, operands: [] };
    return plainToInstance(FilterOperationDto, plainDto);
  }

  const operator = value.substring(0, colonIndex);
  const concOperands = value.substring(colonIndex + 1);

  const operandsStr = concOperands.split(',');
  const operands = operandsStr.map((operand) => +operand);

  const plainDto = { operator, operands };
  return plainToInstance(FilterOperationDto, plainDto);
};

export const ToFilterOperationDto = () =>
  Transform(({ value }) => toFilterOperationDto(value));
```

> If you wish, you may visit the [TypeScript playground](https://www.typescriptlang.org/play) to test this logic yourself.

We may then, after all of this, go back to the <mark style="color:blue;">`FilterProductsDto`</mark> and alter the <mark style="color:blue;">`price`</mark> field. It will be transformed into an instance of <mark style="color:blue;">`FilterOperationDto`</mark> and, after that, receive its validation.

```typescript
@IsOptional()
@ValidateNested()
@ToFilterOperationDto()
readonly price?: FilterOperationDto;
```

{% hint style="info" %}
Remember that transformation always takes place **before** validation.
{% endhint %}

We're almost done. The DTO is correctly transformed and validated. The last step is to actually use it. Luckily, this is the easiest step. Back in the <mark style="color:blue;">`FilteringService`</mark>, let's create a method that receives this DTO and returns the corresponding TypeORM filter operation. Nothing out of this world here.

```typescript
compare(filterOperationDto: FilterOperationDto) {
  if (!filterOperationDto) return;

  const { operator, operands } = filterOperationDto;
  const [operand, secondOperand] = operands;

  switch (operator) {
    case 'lt':
      return LessThan(operand);
    case 'lte':
      return LessThanOrEqual(operand);
    case 'gt':
      return MoreThan(operand);
    case 'gte':
      return MoreThanOrEqual(operand);
    case 'eq':
      return Equal(operand);
    case 'btw':
      return Between(operand, secondOperand);

    default:
      const exhaustiveCheck: never = operator;
      return exhaustiveCheck;
  }
}
```

Then, we just need to use it in the <mark style="color:blue;">`findAll()`</mark> method in the <mark style="color:blue;">`ProductsService`</mark>, and we're finished!

```typescript
price: this.filteringService.compare(price),
```

<mark style="color:green;">**Commit**</mark> - Filtering by price with filter operators
