Skip to main content
The RequestQueryBuilder class provides a fluent, chainable API for building complex queries from your frontend application. It generates properly formatted query strings that the NestJS CRUD backend can parse and execute.

Installation

The RequestQueryBuilder is part of the @nestjsx/crud-request package:
npm install @nestjsx/crud-request

Basic Usage

1

Import the builder

import { RequestQueryBuilder } from '@nestjsx/crud-request';
2

Create a new instance

const qb = RequestQueryBuilder.create();
3

Chain methods and generate query string

const queryString = qb
  .select(['id', 'name', 'email'])
  .setFilter({ field: 'isActive', operator: '$eq', value: true })
  .setLimit(10)
  .query();

Creating a Query Builder

Default Constructor

Create a new instance with default configuration:
const qb = RequestQueryBuilder.create();

From Parameters Object

Create and configure in one step using a parameters object:
const qb = RequestQueryBuilder.create({
  fields: ['id', 'name', 'email'],
  filter: [{ field: 'age', operator: '$gte', value: 18 }],
  sort: [{ field: 'name', order: 'ASC' }],
  limit: 20,
  page: 1,
});

const queryString = qb.query();
The create() method with parameters internally calls select(), setFilter(), sortBy(), and other methods based on the provided configuration.

Selecting Fields

Control which fields are returned in the response:
const qb = RequestQueryBuilder.create()
  .select(['id', 'name', 'email', 'createdAt']);
const qb = RequestQueryBuilder.create()
  .select(['id', 'name', 'email']);

// Generates: fields=id,name,email

Filtering Data

Single Filter

Apply a single filter condition:
const qb = RequestQueryBuilder.create()
  .setFilter({ field: 'status', operator: '$eq', value: 'active' });

// Generates: filter=status||$eq||active

Multiple Filters (AND)

Multiple filters are combined with AND logic:
const qb = RequestQueryBuilder.create()
  .setFilter([
    { field: 'status', operator: '$eq', value: 'active' },
    { field: 'age', operator: '$gte', value: 18 },
    { field: 'role', operator: '$ne', value: 'guest' },
  ]);

// All conditions must be true

Array Notation

Use array notation for more concise syntax:
const qb = RequestQueryBuilder.create()
  .setFilter(['status', '$eq', 'active']);

// Equivalent to:
// .setFilter({ field: 'status', operator: '$eq', value: 'active' })

OR Filters

Use setOr() for OR logic:
const qb = RequestQueryBuilder.create()
  .setOr([
    { field: 'role', operator: '$eq', value: 'admin' },
    { field: 'role', operator: '$eq', value: 'moderator' },
  ]);

// Generates: or=role||$eq||admin&or=role||$eq||moderator

Combining AND and OR

const qb = RequestQueryBuilder.create()
  .setFilter({ field: 'status', operator: '$eq', value: 'active' })
  .setOr([
    { field: 'role', operator: '$eq', value: 'admin' },
    { field: 'role', operator: '$eq', value: 'moderator' },
  ]);

// (status = 'active') AND (role = 'admin' OR role = 'moderator')

Comparison Operators

RequestQueryBuilder.create()
  .setFilter({ field: 'id', operator: '$eq', value: 1 });
  // Equals
  
RequestQueryBuilder.create()
  .setFilter({ field: 'status', operator: '$ne', value: 'deleted' });
  // Not equals
Case-insensitive operators are available by adding ‘L’ suffix: $eqL, $neL, $startsL, $endsL, $contL, $exclL, $inL, $notinL
Use the search() method for complex nested conditions:
const qb = RequestQueryBuilder.create()
  .search({
    $or: [
      { name: { $cont: 'john' } },
      { email: { $cont: 'john' } },
    ],
  });

// Search for 'john' in name OR email
const qb = RequestQueryBuilder.create()
  .search({
    status: 'active',
    $or: [
      { role: 'admin' },
      { 
        $and: [
          { role: 'user' },
          { verified: true },
        ],
      },
    ],
  });
When using search(), any filters set with setFilter() or setOr() will be ignored. The search parameter takes precedence.

Joining Relations

Basic Join

Load related entities:
const qb = RequestQueryBuilder.create()
  .setJoin({ field: 'profile' })
  .setJoin({ field: 'posts' });

// Generates: join=profile&join=posts

Join with Selected Fields

Control which fields are loaded from the relation:
const qb = RequestQueryBuilder.create()
  .setJoin({ 
    field: 'profile', 
    select: ['id', 'avatar', 'bio'] 
  });

// Generates: join=profile||id,avatar,bio

Multiple Joins

const qb = RequestQueryBuilder.create()
  .setJoin([
    { field: 'profile', select: ['id', 'avatar'] },
    { field: 'posts', select: ['id', 'title', 'createdAt'] },
    { field: 'posts.comments' }, // Nested relation
  ]);

Array Notation for Joins

const qb = RequestQueryBuilder.create()
  .setJoin(['profile', ['id', 'avatar', 'bio']]);

// Equivalent to:
// .setJoin({ field: 'profile', select: ['id', 'avatar', 'bio'] })

Sorting Results

Single Sort

const qb = RequestQueryBuilder.create()
  .sortBy({ field: 'createdAt', order: 'DESC' });

// Generates: sort=createdAt,DESC

Multiple Sorts

const qb = RequestQueryBuilder.create()
  .sortBy([
    { field: 'status', order: 'ASC' },
    { field: 'createdAt', order: 'DESC' },
  ]);

// Sort by status first, then by createdAt

Array Notation for Sorting

const qb = RequestQueryBuilder.create()
  .sortBy(['name', 'ASC']);

// Equivalent to:
// .sortBy({ field: 'name', order: 'ASC' })

Pagination

Limit and Offset

const qb = RequestQueryBuilder.create()
  .setLimit(20)
  .setOffset(40);

// Get 20 items starting from position 40

Page-based Pagination

const qb = RequestQueryBuilder.create()
  .setLimit(20)
  .setPage(3);

// Get page 3 with 20 items per page
When using setPage(), the offset is automatically calculated as (page - 1) * limit.

Cache Control

Reset Cache

Force a fresh query bypassing any server-side cache:
const qb = RequestQueryBuilder.create()
  .resetCache();

// Generates: cache=0

Soft Deletes

Include soft-deleted records in results:
const qb = RequestQueryBuilder.create()
  .setIncludeDeleted(1);

// Generates: include_deleted=1

Complete Example

Here’s a comprehensive example combining multiple features:
import { RequestQueryBuilder } from '@nestjsx/crud-request';

const qb = RequestQueryBuilder.create()
  // Select specific fields
  .select(['id', 'name', 'email', 'createdAt'])
  
  // Join relations with specific fields
  .setJoin([
    { field: 'profile', select: ['id', 'avatar', 'bio'] },
    { field: 'posts', select: ['id', 'title', 'publishedAt'] },
  ])
  
  // Filter conditions (AND)
  .setFilter([
    { field: 'isActive', operator: '$eq', value: true },
    { field: 'age', operator: '$gte', value: 18 },
  ])
  
  // OR conditions
  .setOr([
    { field: 'role', operator: '$eq', value: 'admin' },
    { field: 'role', operator: '$eq', value: 'moderator' },
  ])
  
  // Sorting
  .sortBy([
    { field: 'createdAt', order: 'DESC' },
    { field: 'name', order: 'ASC' },
  ])
  
  // Pagination
  .setLimit(20)
  .setPage(1)
  
  // Generate query string
  .query();

// Use with your HTTP client
const response = await fetch(`/api/users?${queryString}`);

Using with HTTP Clients

const qb = RequestQueryBuilder.create()
  .select(['id', 'name'])
  .setFilter({ field: 'isActive', operator: '$eq', value: true })
  .setLimit(10);

const response = await fetch(`/api/users?${qb.query()}`);
const data = await response.json();

Configuration

Global Options

Configure the builder globally for your application:
import { RequestQueryBuilder } from '@nestjsx/crud-request';

RequestQueryBuilder.setOptions({
  delim: '||',      // Delimiter for query parts
  delimStr: ',',    // Delimiter for arrays
  paramNamesMap: {
    fields: ['fields', 'select'],
    filter: 'filter',
    or: 'or',
    join: 'join',
    sort: 'sort',
    limit: ['limit', 'per_page'],
    offset: 'offset',
    page: 'page',
    cache: 'cache',
    includeDeleted: 'include_deleted',
  },
});

Custom Parameter Names

If your API uses different parameter names:
RequestQueryBuilder.setOptions({
  paramNamesMap: {
    fields: 'select',
    filter: 'where',
    limit: 'take',
    offset: 'skip',
  },
});

const qb = RequestQueryBuilder.create()
  .select(['id', 'name'])
  .setLimit(10);

// Generates: select=id,name&take=10

Accessing Query Object

Access the raw query object before converting to string:
const qb = RequestQueryBuilder.create()
  .select(['id', 'name'])
  .setFilter({ field: 'status', operator: '$eq', value: 'active' });

console.log(qb.queryObject);
// {
//   fields: 'id,name',
//   filter: ['status||$eq||active']
// }

Query String Generation

The query() method generates the final URL query string:
const qb = RequestQueryBuilder.create()
  .select(['id', 'name'])
  .setFilter({ field: 'status', operator: '$eq', value: 'active' });

// URL encoded (default)
const encoded = qb.query();
// fields=id%2Cname&filter=status%7C%7C%24eq%7C%7Cactive

// Without encoding
const notEncoded = qb.query(false);
// fields=id,name&filter=status||$eq||active
The generated query string is also stored in the queryString property: qb.queryString

TypeScript Types

The builder is fully typed for TypeScript users:
import {
  RequestQueryBuilder,
  QueryFilter,
  QueryJoin,
  QuerySort,
  CondOperator,
  CreateQueryParams,
} from '@nestjsx/crud-request';

// Typed filter
const filter: QueryFilter = {
  field: 'status',
  operator: CondOperator.EQUALS, // or '$eq'
  value: 'active',
};

// Typed join
const join: QueryJoin = {
  field: 'profile',
  select: ['id', 'avatar'],
};

// Typed sort
const sort: QuerySort = {
  field: 'createdAt',
  order: 'DESC',
};

// Use with builder
const qb = RequestQueryBuilder.create()
  .setFilter(filter)
  .setJoin(join)
  .sortBy(sort);

Best Practices

  1. Reusable Query Builders: Create factory functions for common queries
function createActiveUsersQuery(page: number) {
  return RequestQueryBuilder.create()
    .select(['id', 'name', 'email'])
    .setFilter({ field: 'isActive', operator: '$eq', value: true })
    .sortBy({ field: 'name', order: 'ASC' })
    .setLimit(20)
    .setPage(page);
}
  1. Type-Safe Filters: Use TypeScript enums for operators
import { CondOperator } from '@nestjsx/crud-request';

const qb = RequestQueryBuilder.create()
  .setFilter({ 
    field: 'status', 
    operator: CondOperator.EQUALS, 
    value: 'active' 
  });
  1. Dynamic Queries: Build queries based on user input
function buildUserQuery(options: {
  search?: string;
  role?: string;
  page?: number;
}) {
  const qb = RequestQueryBuilder.create()
    .select(['id', 'name', 'email', 'role']);
  
  if (options.search) {
    qb.search({
      $or: [
        { name: { $cont: options.search } },
        { email: { $cont: options.search } },
      ],
    });
  }
  
  if (options.role) {
    qb.setFilter({ field: 'role', operator: '$eq', value: options.role });
  }
  
  if (options.page) {
    qb.setPage(options.page).setLimit(20);
  }
  
  return qb.query();
}

Next Steps

Query String Format

Learn about the query string format and parameters

Controllers

Set up CRUD controllers to handle these queries

Build docs developers (and LLMs) love