nestjs-rest-querynestjs-rest-query
Primeiros PassosInstalação

Instalação

Como instalar e configurar o nestjs-rest-query no seu projeto.

Instale o pacote

pnpm add nestjs-rest-query@alpha

A 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/typeorm

Configure 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

main.ts
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:

app.module.ts
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çãoTipoDefaultDescrição
defaultPerPagenumber10Itens por página quando a requisição não traz perPage.
maxPerPagenumber500Teto. Um perPage maior é 400, não um corte silencioso.
allowUnpaginatedbooleantruefalse recusa ?paginate=false com 400 em todo endpoint.
maxUnpaginatedRowsnumbermaxPerPageTeto 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çãoTipoDefaultDescrição
enabledbooleanfalseHabilita os logs internos.
level'error' | 'warn' | 'info' | 'debug''info'Nível mínimo emitido.
redactValuesbooleantrueRedige valores de filtro e de busca antes de logar.
loggerLoggerLikeLogger NestJSQualquer 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çãoTipoDefaultDescrição
enforcebooleanfalseExige 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çãoTipoDefault
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çãoTipoDefault
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.

Editar esta página no GitHub

On this page