Instalação
Como instalar e configurar o nestjs-rest-query no seu projeto.
Instale o pacote
pnpm add nestjs-rest-query@alphaA tag @alpha não é opcional enquanto a 3.x for prerelease. Hoje ela
resolve para 3.0.0-alpha.0, que é a API descrita nestas páginas; a latest
continua apontando para a 2.1.0, então omitir a tag instala a API 2.x e
nenhum código daqui compila contra ela.
Depois instale o ORM que você usa. Todos os peers de ORM são opcionais e o entrypoint raiz não carrega nenhum, então você instala exatamente um:
pnpm add typeorm @nestjs/typeormConfigure o bootstrap
Três ajustes são necessários no main.ts para a biblioteca funcionar
corretamente.
1. Query parser estendido
app.set('query parser', 'extended');O Express 5 trocou o parser de query padrão de qs (estendido) para simple. A
biblioteca espera que parâmetros como ?filter[name][eq]=foo sejam expandidos
em objetos aninhados pelo framework antes de chegarem ao controller — e isso só
acontece com o parser extended. Sem ele, um filtro é aceito em silêncio e
nunca aplicado.
2. ValidationPipe com conversão implícita
app.useGlobalPipes(
new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
})
);A DynamicQueryDto tipa page, perPage e paginate como string, porque é
isso que chega no fio. A biblioteca faz o parse e a validação por conta própria —
?page=abc é 400 PAGINATION_INVALID, nunca uma queda silenciosa para a
página 1.
3. Interceptor do Swagger (opcional)
import { dqbSwaggerRequestInterceptor } from 'nestjs-rest-query';
SwaggerModule.setup('/', app, document, {
swaggerOptions: {
requestInterceptor: dqbSwaggerRequestInterceptor(document),
},
});A Swagger UI serializa filtros como vários query params
(filter[name][eq]=foo). Sem o interceptor, o navegador envia esses parâmetros
de uma forma em que o Express pode não preservar corretamente a notação de
colchetes. O dqbSwaggerRequestInterceptor reescreve a URL antes do envio,
garantindo que os filtros cheguem ao controller no formato esperado.
Este interceptor só é necessário para testar filtros pela Swagger UI. Clientes externos (Postman, aplicações de frontend, etc.) montam a URL diretamente e não precisam dele. A seção Swagger explica o cenário completo.
Exemplo completo
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { dqbSwaggerRequestInterceptor } from 'nestjs-rest-query';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// Necessário para expandir filter[campo][op]=valor em objetos aninhados
app.set('query parser', 'extended');
app.useGlobalPipes(
new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
})
);
const document = SwaggerModule.createDocument(
app,
new DocumentBuilder().setTitle('Minha API').setVersion('1.0').build()
);
SwaggerModule.setup('/', app, document, {
swaggerOptions: {
requestInterceptor: dqbSwaggerRequestInterceptor(document),
},
});
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();Registre o módulo
Importe o DynamicQueryBuilderModule no AppModule raiz com forRoot. Toda
opção é opcional — forRoot({}) já entrega os defaults:
import { Module } from '@nestjs/common';
import { DynamicQueryBuilderModule } from 'nestjs-rest-query';
@Module({
imports: [
DynamicQueryBuilderModule.forRoot({
pagination: { defaultPerPage: 10, maxPerPage: 500 },
}),
],
})
export class AppModule {}O módulo é @Global, então você não precisa importá-lo nos módulos de feature —
o QueryBuilderService fica disponível na aplicação inteira automaticamente.
Nenhum adapter aqui
O forRoot configura apenas política comum. Não existe opção adapter nem
adapter default implícito: quem decide o adapter é a source que você passa
ao execute(). Passar adapter ou operators é recusado na inicialização com
SOURCE_CONFIGURATION_INVALID e a mensagem
forRoot no longer accepts "adapter"; see the "2.x → 3.x" section of MIGRATION.md.
Opções de configuração
O forRoot aceita um objeto QueryBuilderConfigV3.
pagination
| Opção | Tipo | Default | Descrição |
|---|---|---|---|
defaultPerPage | number | 10 | Itens por página quando a requisição não traz perPage. |
maxPerPage | number | 500 | Teto. Um perPage maior é 400, não um corte silencioso. |
allowUnpaginated | boolean | true | false recusa ?paginate=false com 400 em todo endpoint. |
maxUnpaginatedRows | number | maxPerPage | Teto de linhas de uma resposta paginate=false; acima dele é 400, não corte. |
Todas são validadas na inicialização: defaultPerPage abaixo de 1, um
maxPerPage menor que o defaultPerPage, um allowUnpaginated não booleano ou
um maxUnpaginatedRows que não seja inteiro positivo estoura
SOURCE_CONFIGURATION_INVALID enquanto o módulo carrega. Um endpoint pode
substituir as duas chaves de paginate=false nas regras — veja
Paginação.
logging
| Opção | Tipo | Default | Descrição |
|---|---|---|---|
enabled | boolean | false | Habilita os logs internos. |
level | 'error' | 'warn' | 'info' | 'debug' | 'info' | Nível mínimo emitido. |
redactValues | boolean | true | Redige valores de filtro e de busca antes de logar. |
logger | LoggerLike | Logger NestJS | Qualquer objeto com error/warn/log/debug (winston, pino, ...). |
DynamicQueryBuilderModule.forRoot({
logging: { enabled: true, level: 'debug' },
});O log do plano em debug carrega apenas metadados — paths, operadores,
contagens, paginação — nunca os valores que o cliente enviou.
portability
| Opção | Tipo | Default | Descrição |
|---|---|---|---|
enforce | boolean | false | Exige que a source carregue os fatos do perfil certificado, e os verifica. |
Com enforce: true, uma source construída sem portabilityProfile é recusada
com PORTABILITY_PROFILE_MISMATCH, e o mesmo vale para um perfil cujo dialeto
não bata com o do adapter ou que falhe em checkPortabilityProfile(). Colete os
fatos uma vez, na subida, com collectProfileFacts(), e entregue-os à fábrica
da source — o caminho de requisição nunca consulta catálogos.
consistency
| Opção | Tipo | Default |
|---|---|---|
consistency | 'eventual' | 'transactional' | 'eventual' |
Nenhum adapter entregue suporta 'transactional' hoje, então o forRoot
recusa o valor: TypeORM, Prisma e Drizzle reportam transactionalConsistency: false, e uma configuração que reprovaria toda requisição morre na
inicialização com SOURCE_CONFIGURATION_INVALID. O 'eventual' é o único
valor que sobe.
textProfile
| Opção | Tipo | Default |
|---|---|---|
textProfile | 'portable-strict' | 'database-native' | 'portable-strict' |
Sob portable-strict, ilike, notIlike e search comparam uma coluna
dobrada declarada com um termo dobrado, então nenhum ILIKE e nenhum
mode: 'insensitive' é emitido.
O 'database-native' é reservado, e o forRoot o recusa com
SOURCE_CONFIGURATION_INVALID. Nenhum adapter lê o valor — a dobra é aplicada
pelo núcleo de qualquer forma —, então aceitá-lo significaria perder a
verificação de portability.enforce em silêncio e ainda assim compilar
portable-strict. O 'portable-strict' é o único valor que sobe.
Próximos passos
Com o módulo registrado, injete o QueryBuilderService em qualquer provider,
declare um schema e as regras do endpoint, e passe uma source ao execute().
Continue em Primeiro endpoint para uma rota funcionando de ponta a ponta, ou em Adapters para ver o que cada ORM exige.