Skip to main content
NestJS CRUD uses a specific query string format to represent complex database queries. Understanding this format helps you build URLs manually or debug issues with the RequestQueryBuilder.

Query String Structure

The query string format follows these conventions:
  • Parameters are separated by &
  • Array values are represented by multiple parameters with the same name
  • The default delimiter for field operators is ||
  • The default delimiter for array values is ,
/api/users?fields=id,name,email&filter=status||$eq||active&sort=name,ASC&limit=10

Available Parameters

fields (select)

Select specific fields to include in the response.
/api/users?fields=id,name,email
Format: fields=field1,field2,field3
If no fields are specified, all fields are returned by default (unless restricted by the backend).

filter

Apply AND filter conditions. Format: filter=field||operator||value
/api/users?filter=status||$eq||active

or

Apply OR filter conditions. Format: or=field||operator||value
OR Conditions
/api/users?or=role||$eq||admin&or=role||$eq||moderator

Combining AND and OR

Mixed Conditions
/api/users?filter=status||$eq||active&or=role||$eq||admin&or=role||$eq||moderator
This translates to: (status = 'active') AND (role = 'admin' OR role = 'moderator')

Filter Operators

Equality Operators

OperatorDescriptionExample
$eqEquals`filter=id$eq1`
$neNot equals`filter=status$nedeleted`
$eqLEquals (case-insensitive)`filter=name$eqLjohn`
$neLNot equals (case-insensitive)`filter=role$neLadmin`

Comparison Operators

OperatorDescriptionExample
$gtGreater than`filter=age$gt18`
$ltLower than`filter=age$lt65`
$gteGreater than or equal`filter=price$gte100`
$lteLower than or equal`filter=price$lte1000`

String Operators

OperatorDescriptionExample
$startsStarts with`filter=name$startsJohn`
$endsEnds with`filter=email$ends@gmail.com`
$contContains`filter=description$contnestjs`
$exclExcludes`filter=tags$excldeprecated`
$startsLStarts with (case-insensitive)`filter=name$startsLjohn`
$endsLEnds with (case-insensitive)`filter=email$endsL@GMAIL.COM`
$contLContains (case-insensitive)`filter=description$contLNestJS`
$exclLExcludes (case-insensitive)`filter=tags$exclLDEPRECATED`

Array Operators

OperatorDescriptionExample
$inIn array`filter=status$inactive,pending`
$notinNot in array`filter=role$notinguest,banned`
$inLIn array (case-insensitive)`filter=role$inLAdmin,Moderator`
$notinLNot in array (case-insensitive)`filter=status$notinLDELETED,BANNED`

Null Operators

OperatorDescriptionExample
$isnullIs null`filter=deletedAt$isnull`
$notnullNot null`filter=email$notnull`

Range Operator

OperatorDescriptionExample
$betweenBetween two values`filter=age$between18,65`
Operators ending with ‘L’ (like $eqL, $contL) perform case-insensitive comparisons. Use these for user-friendly search functionality.

Advanced Search (s)

The s (search) parameter accepts complex nested JSON conditions. Format: s={"field":"value"} (URL encoded)
/api/users?s={"status":"active"}
When the s parameter is present, filter and or parameters are ignored. The search parameter takes precedence.

Search Structure Examples

Simple Field Matching
{
  "name": "John",
  "status": "active"
}
Field with Operators
{
  "age": {
    "$gte": 18,
    "$lte": 65
  },
  "email": {
    "$notnull": true
  }
}
OR Conditions
{
  "$or": [
    { "role": "admin" },
    { "role": "moderator" }
  ]
}
AND Conditions
{
  "$and": [
    { "status": "active" },
    { "verified": true }
  ]
}
Complex Nested Logic
{
  "status": "active",
  "$or": [
    { "role": "admin" },
    {
      "$and": [
        { "role": "user" },
        { "verified": true },
        { "age": { "$gte": 18 } }
      ]
    }
  ]
}

Join Relations

Load related entities with their data. Format: join=relation or join=relation||field1,field2
/api/users?join=profile&join=posts
Always specify the fields you need from relations to reduce payload size and improve performance.

Sorting

Sort results by one or more fields. Format: sort=field,ORDER Orders: ASC (ascending) or DESC (descending)
/api/users?sort=name,ASC

Pagination

Limit

Limit the number of results returned. Format: limit=number
Set Limit
/api/users?limit=20
Alternative: per_page=20

Offset

Skip a specific number of records. Format: offset=number
Set Offset
/api/users?limit=20&offset=40
This returns 20 results starting from position 40 (skips first 40).

Page

Use page-based pagination. Format: page=number
Page-based Pagination
/api/users?limit=20&page=3
This returns page 3 with 20 items per page (items 41-60).
When using page, the offset is calculated as (page - 1) * limit. You should always specify a limit when using page.

Cache Control

Control server-side caching behavior. Format: cache=0 or cache=1
Reset Cache
/api/users?cache=0
Setting cache=0 forces a fresh database query, bypassing any cached results.

Soft Deletes

Include soft-deleted records in the results. Format: include_deleted=number
/api/users?include_deleted=1
This only works if your entity has soft delete enabled. See the Soft Delete guide for more information.

Complete Examples

Example 1: Basic Filtering and Pagination

/api/users?fields=id,name,email&filter=isActive||$eq||true&sort=name,ASC&limit=20&page=1
Breakdown:
  • Select fields: id, name, email
  • Filter: isActive = true
  • Sort by name ascending
  • Return 20 items from page 1

Example 2: Complex Filtering with Relations

/api/posts?fields=id,title,publishedAt&join=author||id,name&join=comments&filter=status||$eq||published&filter=publishedAt||$gte||2024-01-01&sort=publishedAt,DESC&limit=10
Breakdown:
  • Select post fields: id, title, publishedAt
  • Join author relation (only id and name)
  • Join all comments
  • Filter: status = ‘published’ AND publishedAt >= ‘2024-01-01’
  • Sort by publishedAt descending
  • Limit to 10 results

Example 3: Search with OR Conditions

/api/users?fields=id,name,email&or=role||$eq||admin&or=role||$eq||moderator&filter=status||$eq||active&sort=createdAt,DESC
Breakdown:
  • Select fields: id, name, email
  • Filter: status = ‘active’ AND (role = ‘admin’ OR role = ‘moderator’)
  • Sort by createdAt descending

Example 4: Advanced Search Query

/api/products?s={"$or":[{"name":{"$cont":"laptop"}},{"description":{"$cont":"laptop"}}],"price":{"$between":[500,2000]},"inStock":true}&sort=price,ASC
Breakdown (URL decoded):
{
  "$or": [
    { "name": { "$cont": "laptop" } },
    { "description": { "$cont": "laptop" } }
  ],
  "price": { "$between": [500, 2000] },
  "inStock": true
}
  • Search: (name contains ‘laptop’ OR description contains ‘laptop’) AND price between 500-2000 AND inStock = true
  • Sort by price ascending

Example 5: Nested Relations

/api/users?join=posts&join=posts.comments&join=posts.comments.author||id,name&filter=posts.status||$eq||published&sort=posts.publishedAt,DESC
Breakdown:
  • Join posts relation
  • Join comments relation from posts
  • Join author relation from comments (only id and name)
  • Filter posts where status = ‘published’
  • Sort by post publishedAt descending

URL Encoding

When building URLs manually, remember to encode special characters:
CharacterEncoded
||%7C%7C
,%2C
$%24
{%7B
}%7D
"%22
:%3A
[%5B
]%5D
Example: Unencoded:
/api/users?filter=name||$cont||John
Encoded:
/api/users?filter=name%7C%7C%24cont%7C%7CJohn
The RequestQueryBuilder handles URL encoding automatically when you call .query() or .query(true). Use .query(false) for unencoded strings.

Custom Parameter Names

You can configure custom parameter names on both frontend and backend:
Frontend Configuration
import { RequestQueryBuilder } from '@nestjsx/crud-request';

RequestQueryBuilder.setOptions({
  paramNamesMap: {
    fields: 'select',
    filter: 'where',
    limit: 'take',
    offset: 'skip',
  },
});
Backend Configuration
import { CrudConfigService } from '@nestjsx/crud';

CrudConfigService.load({
  query: {
    fields: 'select',
    filter: 'where',
    limit: 'take',
    offset: 'skip',
  },
});
With these configurations, your URLs would look like:
Custom Parameter Names
/api/users?select=id,name&where=status||$eq||active&take=10&skip=20

Debugging Query Strings

Browser Console

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

// View query object
console.log('Query Object:', qb.queryObject);

// View encoded query string
console.log('Encoded:', qb.query());

// View unencoded query string
console.log('Unencoded:', qb.query(false));

Network Tab

Inspect the actual request in your browser’s Network tab to see the final URL with all query parameters.

Best Practices

1

Use RequestQueryBuilder

Always use RequestQueryBuilder instead of manually constructing query strings to avoid encoding issues and syntax errors.
2

Select Only Needed Fields

Always specify the fields you need rather than fetching all fields, especially for large entities.
3

Limit Joined Relations

When joining relations, specify which fields to include to reduce payload size.
4

Use Pagination

Always implement pagination for list endpoints to prevent performance issues with large datasets.
5

Index Filtered Fields

Ensure database indexes exist on fields frequently used in filters and sorts for better performance.

Next Steps

RequestQueryBuilder

Learn how to use the RequestQueryBuilder class

Controllers

Set up CRUD controllers to handle these queries