nestjs-rest-querynestjs-rest-query
Getting StartedInstallation

Installation

How to install and configure nestjs-rest-query in your project.

Install the package

pnpm add nestjs-rest-query

Configure bootstrap

Three adjustments are required in main.ts for the library to work correctly.

1. Extended query parser

app.set('query parser', 'extended');

Express 5 changed the default query parser from qs (extended) to simple. The library expects parameters like ?filter[name][eq]=foo to be expanded into nested objects by the framework before reaching the controller — that only happens with the extended parser.

2. ValidationPipe with implicit conversion

app.useGlobalPipes(
  new ValidationPipe({
    transform: true,
    transformOptions: {
      enableImplicitConversion: true,
    },
  })
);

The DTO uses string for fields like page and perPage. enableImplicitConversion: true instructs class-transformer to convert those values to the expected types automatically, without requiring additional decorators in the DTO.

3. Swagger interceptor (optional)

import { dqbSwaggerRequestInterceptor } from 'nestjs-rest-query';

SwaggerModule.setup('/', app, document, {
  swaggerOptions: {
    requestInterceptor: dqbSwaggerRequestInterceptor(document),
  },
});

Swagger UI serializes filters as multiple query params (filter[name][eq]=foo). Without the interceptor, the browser sends those parameters in a way that Express may not preserve the bracket notation correctly. dqbSwaggerRequestInterceptor rewrites the URL before sending the request, ensuring the filters reach the controller in the expected format.

This interceptor is only necessary to test filters through the Swagger UI. External clients (Postman, frontend applications, etc.) build the URL directly and do not need it. The Swagger section explains the full scenario.

Full example

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);

  // Required to expand filter[field][op]=value into nested objects
  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();

Register the module

Import DynamicQueryBuilderModule in the root AppModule with forRoot. All options are optional — without configuration, the library uses the default values:

app.module.ts
import { Module } from '@nestjs/common';
import { DynamicQueryBuilderModule } from 'nestjs-rest-query';

@Module({
  imports: [
    DynamicQueryBuilderModule.forRoot({
      // all options are optional
    }),
  ],
})
export class AppModule {}

The module is @Global, so you do not need to import it in feature modules — QueryBuilderService becomes available across the entire application automatically.

Configuration options

forRoot accepts a QueryBuilderConfig object. All properties are optional.

pagination

Controls the default pagination behavior.

OptionTypeDefaultDescription
defaultPerPagenumber10Number of items per page when perPage is not provided in the request.
maxPerPagenumber100Maximum number of items per page, ignoring larger values sent by the client.

operators

Restricts the allowed filter operators globally in the application.

OptionTypeDefaultDescription
allowedQueryOperator[]all allowedList of accepted operators. undefined = all. [] (empty list) = no operators allowed.
DynamicQueryBuilderModule.forRoot({
  operators: {
    allowed: ['eq', 'like', 'in', 'between'],
  },
});

You can also override this per endpoint with RulesConfig.operators; when present, the endpoint value wins over the global forRoot setting for that route.

@ApiDynamicQuery({
  filters: ['name', 'status'],
  operators: { allowed: ['eq', 'ilike'] },
})
list(@Query() query: DynamicQueryDto, @QueryRules() rules: RulesConfig) {
  return this.service.list(query, rules);
}

If the endpoint explicitly sets operators: {}, it resets that route to all operators allowed.

logging

Enables and configures internal query-building logs.

OptionTypeDefaultDescription
enabledbooleanfalseEnables internal logs.
level'error' | 'warn' | 'info' | 'debug''info'Minimum emitted log level.
loggerLoggerLikeNestJS LoggerCustom logger. Any object with error/warn/log/debug works (winston, pino, etc.).
DynamicQueryBuilderModule.forRoot({
  logging: {
    enabled: true,
    level: 'debug',
  },
});

Next steps

With the module registered, inject QueryBuilderService into any provider and start building dynamic queries from HTTP request parameters.

If you want to start from zero with a real endpoint, continue to First endpoint. To understand the OpenAPI integration, see Swagger.

Edit this page on GitHub

On this page