Aviary
Guides

Using Filters in Services

How to apply filters programmatically in services using @ApplyFilter (controller) and FilterRunner.apply() (service).

nestjs-filter provides two primary APIs for applying filters:

  1. @ApplyFilter -- parameter decorator for controllers
  2. FilterRunner.apply() -- programmatic use in services

There is no need for repository wrapper classes. You create a QueryBuilder from your ORM, pass it to FilterRunner.apply(), and continue chaining or executing as usual.


Controller: @ApplyFilter

The @ApplyFilter parameter decorator resolves input from the request, runs the filter, and injects the resulting QueryBuilder directly:

import { Controller, Get } from '@nestjs/common';
import { ApplyFilter } from '@dudousxd/nestjs-filter';
import type { QueryBuilder } from '@mikro-orm/sql';
import { User } from './user.entity.js';
import { UserFilter } from './user.filter.js';

@Controller('users')
export class UsersController {
  @Get()
  list(@ApplyFilter(UserFilter) qb: QueryBuilder<User>) {
    return qb.getResultList();
  }
}
import { Controller, Get } from '@nestjs/common';
import { ApplyFilter } from '@dudousxd/nestjs-filter';
import type { SelectQueryBuilder } from 'typeorm';
import { User } from './user.entity.js';
import { UserFilter } from './user.filter.js';

@Controller('users')
export class UsersController {
  @Get()
  list(@ApplyFilter(UserFilter) qb: SelectQueryBuilder<User>) {
    return qb.getMany();
  }
}

Service: FilterRunner.apply()

For programmatic use in services, inject FilterRunner and call .apply() with a QueryBuilder you create yourself:

import { Injectable } from '@nestjs/common';
import { FilterRunner } from '@dudousxd/nestjs-filter';
import type { SqlEntityManager } from '@mikro-orm/sql';
import { User } from './user.entity.js';
import { UserFilter } from './user.filter.js';

@Injectable()
export class UsersService {
  constructor(
    private readonly runner: FilterRunner,
    private readonly em: SqlEntityManager,
  ) {}

  async search(input: Record<string, unknown>) {
    const qb = this.em.createQueryBuilder(User);
    await this.runner.apply(UserFilter, input, qb);
    return qb.getResultList();
  }
}
import { Injectable } from '@nestjs/common';
import { FilterRunner } from '@dudousxd/nestjs-filter';
import { DataSource } from 'typeorm';
import { User } from './user.entity.js';
import { UserFilter } from './user.filter.js';

@Injectable()
export class UsersService {
  constructor(
    private readonly runner: FilterRunner,
    private readonly dataSource: DataSource,
  ) {}

  async search(input: Record<string, unknown>) {
    const qb = this.dataSource.getRepository(User).createQueryBuilder('user');
    await this.runner.apply(UserFilter, input, qb);
    return qb.getMany();
  }
}

This pattern aligns with Spring Boot's Specification approach -- filters are applied to query builders directly, without intermediate repository wrappers.


FilterRegistry

FilterRegistry is a global registry that maps entity classes to their filter classes. It is populated by FilterModule.forFeature() and can be used for auto-lookup:

import { Injectable } from '@nestjs/common';
import { FilterRegistry, FilterRunner } from '@dudousxd/nestjs-filter';
import type { SqlEntityManager } from '@mikro-orm/sql';
import type { Type } from '@nestjs/common';

@Injectable()
export class GenericFilterService {
  constructor(
    private readonly registry: FilterRegistry,
    private readonly runner: FilterRunner,
    private readonly em: SqlEntityManager,
  ) {}

  async filterEntity<E extends object>(
    entity: Type<E>,
    input: Record<string, unknown>,
  ) {
    const FilterClass = this.registry.getFilter(entity);
    if (!FilterClass) throw new Error(`No filter registered for ${entity.name}`);

    const qb = this.em.createQueryBuilder(entity as unknown as new () => E);
    await this.runner.apply(FilterClass as Type<object>, input, qb);
    return qb;
  }
}

FilterRegistry API

MethodDescription
register(entity, filterClass)Maps an entity to a filter class
getFilter(entity)Returns the filter class for an entity, or undefined
has(entity)Returns true if the entity has a registered filter
entries()Returns an immutable ReadonlyMap of all registered mappings

On this page