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:
@ApplyFilter-- parameter decorator for controllersFilterRunner.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
| Method | Description |
|---|---|
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 |