Customizando a Query
Como usar o parâmetro customize do execute() para adicionar condições extras, e como implementar busca textual.
O método execute() aceita um quarto parâmetro opcional: um callback que recebe o SelectQueryBuilder já com filtros, sorts, includes e fields aplicados, permitindo adicionar cláusulas extras antes da execução.
Regra prática: nos parâmetros públicos da biblioteca (filters, sorts,
fields, includes, search) use nomes de propriedades da entidade. No
customize, quando você escrever SQL manual em string, pode usar os nomes
físicos de coluna do banco.
execute(repo, query, rules, customize?: (qb: SelectQueryBuilder<T>) => void)Quando usar
| Caso de uso | Abordagem |
|---|---|
| Condições internas não expostas ao cliente (soft delete, tenant, status ativo) | customize |
| Busca textual fora do padrão da lib | customize |
Filtrar pelo usuário autenticado (userId = :id) | customize |
| JOINs manuais que a lib não cobre | customize |
| Filtros que o cliente deve controlar | @ApiDynamicQuery com filters |
| Carregamento de relações | includes no RulesConfig |
Exemplo básico
Forçar uma condição que nunca deve ser exposta como filtro público:
@Get()
@ApiDynamicQuery({
filters: ['name', 'email'],
sorts: ['name', 'createdAt'],
})
async findAll(
@Query() query: DynamicQueryDto,
@QueryRules() rules: RulesConfig,
) {
return this.queryBuilderService.execute(
this.usersRepo,
query,
rules,
(qb) => {
qb.andWhere('root.status = :status', { status: 'active' });
},
);
}Busca textual (search)
A biblioteca agora possui search nativo. Basta declarar os campos pesquisáveis em RulesConfig.search e enviar ?search=termo:
@Get()
@ApiDynamicQuery({
filters: ['name', 'createdAt'],
sorts: ['name', 'createdAt'],
search: ['name', 'email', 'company.name'],
})
async findAll(
@Query() query: DynamicQueryDto,
@QueryRules() rules: RulesConfig,
) {
return this.queryBuilderService.execute(this.usersRepo, query, rules);
}Uso: GET /users?search=john
Os campos em search são combinados com OR, usando busca textual case-insensitive. Isso também funciona para relações aninhadas como company.name e items.company.cnpj — a lib reaproveita joins de includes quando existirem e cria joins simples quando necessário.
search é pensado para uma caixa de busca rápida. O cliente envia apenas o
termo; o backend decide quais campos podem ser pesquisados.
Quando ainda usar customize para busca
Use customize quando a busca precisar fugir do comportamento padrão da lib, por exemplo:
- pesos diferentes por campo
ANDentre grupos de termos- busca full-text específica do banco
- regras condicionais por tenant, role ou contexto de autenticação
Quando usar search em vez de filter
Use filter quando o cliente precisa controlar qual campo filtrar e qual operador usar. Use search quando o frontend só precisa de uma caixa de busca rápida e o backend decide quais campos são pesquisáveis:
filter[name][like]=john | search=john | |
|---|---|---|
| Controle do campo | cliente | backend |
| Whitelist da lib | sim | sim, via RulesConfig.search |
| Múltiplos campos simultâneos | não diretamente | sim (OR) |
| Performance | índice por campo | pode requerer índice composto |
Usando buildQuery para controle total
Prefira buildQuery() quando precisar de controle completo sobre a execução (ex: getManyAndCount, joins complexos):
const qb = this.queryBuilderService.buildQuery(repo, query, rules);
qb.innerJoin('root.company', 'company').andWhere('company.id = :companyId', {
companyId,
});
const [data, total] = await qb.getManyAndCount();buildQuery() retorna o SelectQueryBuilder sem executar a query — todos os filtros, sorts, includes e fields já estão aplicados.