2020-06-26 21:25:03 +00:00
# Nest.js Paginate
![Main CI ](https://github.com/ppetzold/nestjs-paginate/workflows/Main%20CI/badge.svg )
2021-07-20 15:13:52 +00:00
[![npm ](https://img.shields.io/npm/v/nestjs-paginate.svg )](https://www.npmjs.com/package/nestjs-paginate)
[![downloads ](https://img.shields.io/npm/dt/nestjs-paginate.svg )](https://www.npmjs.com/package/nestjs-paginate)
2020-06-26 21:27:43 +00:00
[![codecov ](https://codecov.io/gh/ppetzold/nestjs-paginate/branch/master/graph/badge.svg )](https://codecov.io/gh/ppetzold/nestjs-paginate)
2020-06-26 21:25:03 +00:00
[![code style: prettier ](https://img.shields.io/badge/code_style-prettier-ff69b4.svg )](https://github.com/prettier/prettier)
[![semantic-release ](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg )](https://github.com/semantic-release/semantic-release)
2020-06-26 21:36:41 +00:00
![GitHub ](https://img.shields.io/github/license/ppetzold/nestjs-paginate )
2020-06-26 21:25:03 +00:00
Pagination and filtering helper method for TypeORM repositories or query builders using [Nest.js ](https://nestjs.com/ ) framework.
- Pagination conforms to [JSON:API ](https://jsonapi.org/ )
- Sort by multiple columns
2020-06-28 17:34:00 +00:00
- Search across columns
2023-01-31 16:54:14 +00:00
- Filter using operators (`$eq`, `$not` , `$null` , `$in` , `$gt` , `$gte` , `$lt` , `$lte` , `$btw` , `$ilike` , `$sw` )
2022-03-14 19:23:06 +00:00
- Include relations
2023-01-30 11:23:47 +00:00
- Virtual column support
2020-06-26 21:25:03 +00:00
## Installation
```
npm install nestjs-paginate
```
## Usage
2020-06-26 21:39:18 +00:00
### Example
2020-06-26 21:25:03 +00:00
The following code exposes a route that can be utilized like so:
#### Endpoint
```url
2021-08-19 14:42:18 +00:00
http://localhost:3000/cats?limit=5& page=2& sortBy=color:DESC& search=i& filter.age=$gte:3
2020-06-26 21:25:03 +00:00
```
#### Result
```json
{
"data": [
{
"id": 4,
"name": "George",
2021-08-19 14:42:18 +00:00
"color": "white",
"age": 3
2020-06-26 21:25:03 +00:00
},
{
"id": 5,
"name": "Leche",
2021-08-19 14:42:18 +00:00
"color": "white",
"age": 6
2020-06-26 21:25:03 +00:00
},
{
"id": 2,
"name": "Garfield",
2021-08-19 14:42:18 +00:00
"color": "ginger",
"age": 4
2020-06-26 21:25:03 +00:00
},
{
"id": 1,
"name": "Milo",
2021-08-19 14:42:18 +00:00
"color": "brown",
"age": 5
2020-06-26 21:25:03 +00:00
},
{
"id": 3,
2020-06-28 17:34:00 +00:00
"name": "Kitty",
2021-08-19 14:42:18 +00:00
"color": "black",
"age": 3
2020-06-26 21:25:03 +00:00
}
],
"meta": {
2020-06-27 07:44:48 +00:00
"itemsPerPage": 5,
"totalItems": 12,
2020-06-26 21:25:03 +00:00
"currentPage": 2,
"totalPages": 3,
2020-06-28 17:35:12 +00:00
"sortBy": [["color", "DESC"]],
2021-08-19 14:42:18 +00:00
"search": "i",
"filter": {
"age": "$gte:3"
}
2020-06-26 21:25:03 +00:00
},
"links": {
2021-08-19 14:42:18 +00:00
"first": "http://localhost:3000/cats?limit=5& page=1& sortBy=color:DESC& search=i& filter.age=$gte:3",
"previous": "http://localhost:3000/cats?limit=5& page=1& sortBy=color:DESC& search=i& filter.age=$gte:3",
"current": "http://localhost:3000/cats?limit=5& page=2& sortBy=color:DESC& search=i& filter.age=$gte:3",
"next": "http://localhost:3000/cats?limit=5& page=3& sortBy=color:DESC& search=i& filter.age=$gte:3",
"last": "http://localhost:3000/cats?limit=5& page=3& sortBy=color:DESC& search=i& filter.age=$gte:3"
2020-06-26 21:25:03 +00:00
}
}
```
2022-11-16 07:58:00 +00:00
Array values for filter operators such as `$in` should be provided as comma-separated values:
```
http://localhost:3000/cats?filter.name=$in:George,Milo
```
2020-06-26 21:25:03 +00:00
#### Code
```ts
import { Controller, Injectable, Get } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
2021-08-19 14:42:18 +00:00
import { FilterOperator, Paginate, PaginateQuery, paginate, Paginated } from 'nestjs-paginate'
2021-08-19 17:29:25 +00:00
import { Repository, Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
2020-06-26 21:25:03 +00:00
@Entity ()
export class CatEntity {
@PrimaryGeneratedColumn ()
id: number
@Column ('text')
name: string
@Column ('text')
color: string
2021-08-19 14:42:18 +00:00
@Column ('int')
age: number
2020-06-26 21:25:03 +00:00
}
@Injectable ()
export class CatsService {
constructor(
@InjectRepository (CatEntity)
private readonly catsRepository: Repository< CatEntity >
) {}
public findAll(query: PaginateQuery): Promise< Paginated < CatEntity > > {
return paginate(query, this.catsRepository, {
2021-08-19 17:29:25 +00:00
sortableColumns: ['id', 'name', 'color', 'age'],
2022-08-20 22:25:48 +00:00
nullSort: 'last',
2021-08-19 17:29:25 +00:00
searchableColumns: ['name', 'color', 'age'],
2020-06-28 18:06:34 +00:00
defaultSortBy: [['id', 'DESC']],
2021-08-19 14:42:18 +00:00
filterableColumns: {
age: [FilterOperator.GTE, FilterOperator.LTE],
},
2020-06-26 21:25:03 +00:00
})
}
}
@Controller ('cats')
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Get ()
public findAll(@Paginate() query: PaginateQuery): Promise< Paginated < CatEntity > > {
return this.catsService.findAll(query)
}
}
```
### Config
```ts
const paginateConfig: PaginateConfig< CatEntity > {
/**
* Required: true (must have a minimum of one column)
2020-06-27 07:33:54 +00:00
* Type: (keyof CatEntity)[]
2020-06-26 21:25:03 +00:00
* Description: These are the columns that are valid to be sorted by.
*/
sortableColumns: ['id', 'name', 'color'],
2022-08-22 13:46:23 +00:00
2022-08-20 22:25:48 +00:00
/**
* Required: false
* Type: 'first' | 'last'
2022-08-22 13:46:23 +00:00
* Default: 'first'
* Description: (ONLY WORKS WITH POSTGRES) Define whether to put null values
* at the beginning or end of the result set.
2022-08-20 22:25:48 +00:00
*/
2022-08-20 22:30:34 +00:00
nullSort: 'last',
2020-06-26 21:25:03 +00:00
2021-08-19 17:29:25 +00:00
/**
* Required: false
* Type: [keyof CatEntity, 'ASC' | 'DESC'][]
* Default: [[sortableColumns[0], 'ASC]]
* Description: The order to display the sorted entities.
*/
defaultSortBy: [['name', 'DESC']],
2020-06-28 17:34:00 +00:00
/**
* Required: false
* Type: (keyof CatEntity)[]
2021-10-12 11:38:43 +00:00
* Description: These columns will be searched through when using the search query
* param. Limit search scope further by using `searchBy` query param.
2020-06-28 17:34:00 +00:00
*/
2020-06-28 17:37:19 +00:00
searchableColumns: ['name', 'color'],
2020-06-28 17:34:00 +00:00
2022-07-27 17:58:00 +00:00
/**
* Required: false
* Type: TypeORM partial selection
* Default: None
* https://typeorm.io/select-query-builder#partial-selection
*/
select: ['name', 'color'],
2020-06-26 21:25:03 +00:00
/**
* Required: false
* Type: number
* Default: 100
* Description: The maximum amount of entities to return per page.
2022-12-13 11:35:08 +00:00
* Set it to 0, in conjunction with limit=0 on query param, to disable pagination.
2020-06-26 21:25:03 +00:00
*/
maxLimit: 20,
/**
* Required: false
* Type: number
* Default: 20
*/
defaultLimit: 50,
/**
* Required: false
* Type: TypeORM find options
* Default: None
* https://typeorm.io/#/find-optionsfind-options.md
*/
2021-08-19 14:45:06 +00:00
where: { color: 'ginger' },
2021-08-19 14:42:18 +00:00
/**
* Required: false
2021-08-19 17:29:25 +00:00
* Type: { [key in CatEntity]?: FilterOperator[] } - Operators based on TypeORM find operators
2021-08-19 14:42:18 +00:00
* Default: None
2021-08-19 17:29:25 +00:00
* https://typeorm.io/#/find-options/advanced-options
2021-08-19 14:42:18 +00:00
*/
2022-03-14 19:10:30 +00:00
filterableColumns: { age: [FilterOperator.EQ, FilterOperator.IN] },
2022-03-14 19:02:55 +00:00
/**
* Required: false
* Type: RelationColumn< CatEntity >
* Description: Indicates what relations of entity should be loaded.
*/
relations: [],
2022-05-15 19:01:53 +00:00
/**
* Required: false
* Type: boolean
* Description: Disables the global condition of "non-deleted" for the entity with delete date columns.
* https://typeorm.io/select-query-builder#querying-deleted-rows
*/
withDeleted: false,
2022-09-30 11:11:55 +00:00
/**
* Required: false
* Type: boolean
* Default: false
* Description: Generate relative paths in the resource links.
*/
relativePath: true,
/**
* Required: false
* Type: string
* Description: Overrides the origin of absolute resource links if set.
*/
origin: 'http://cats.example',
2020-06-26 21:25:03 +00:00
}
```
2022-02-06 18:52:38 +00:00
## Usage with Query Builder
2022-03-14 19:10:30 +00:00
You can paginate custom queries by passing on the query builder:
2022-02-06 18:52:38 +00:00
### Example
```typescript
const queryBuilder = repo
.createQueryBuilder('cats')
.leftJoinAndSelect('cats.owner', 'owner')
2022-02-07 13:47:49 +00:00
.where('cats.owner = :ownerId', { ownerId })
2022-02-06 18:52:38 +00:00
const result = await paginate< CatEntity > (query, queryBuilder, config)
```
2022-03-14 19:02:55 +00:00
## Usage with Relations
2022-03-14 19:14:16 +00:00
Similar as with repositories, you can utilize `relations` as a simplified left-join form:
2022-03-14 19:10:30 +00:00
2022-03-14 19:02:55 +00:00
### Example
#### Endpoint
```url
http://localhost:3000/cats?filter.toys.name=$in:Mouse,String
```
#### Code
```typescript
const config: PaginateConfig< CatEntity > = {
relations: ['toys'],
sortableColumns: ['id', 'name', 'toys.name'],
filterableColumns: {
'toys.name': [FilterOperator.IN],
},
}
const result = await paginate< CatEntity > (query, catRepo, config)
```
2022-11-16 07:58:00 +00:00
2023-02-09 08:21:09 +00:00
## Single Filters
2023-01-17 12:47:30 +00:00
Filter operators must be whitelisted per column in `PaginateConfig` .
### Examples
`?filter.name=$eq:Milo` is equivalent with `?filter.name=Milo`
`?filter.age=$btw:4,6` where column `age` is between `4` and `6`
`?filter.id=$not:$in:2,5,7` where column `id` is **not** `2` , `5` or `7`
2023-01-17 12:56:13 +00:00
`?filter.summary=$not:$ilike:term` where column `summary` does **not** contain `term`
2023-01-17 12:47:30 +00:00
2023-01-31 16:54:14 +00:00
`?filter.summary=$sw:term` where column `summary` starts with `term`
2023-01-17 12:47:30 +00:00
`?filter.seenAt=$null` where column `seenAt` is `NULL`
`?filter.seenAt=$not:$null` where column `seenAt` is **not** `NULL`
`?filter.createdAt=$btw:2022-02-02,2022-02-10` where column `createdAt` is between the dates `2022-02-02` and `2022-02-10`
2023-02-09 08:21:09 +00:00
## Multi Filters
Multi filters are filters that can be applied to a single column with a comparator. As for single filters, multi filters must be whitelisted per column in `PaginateConfig` .
### Examples
`?filter.id=$gt:3&filter.id=$lt:5` where column `id` is greater than `3` **and** less than `5`
2023-02-09 08:25:21 +00:00
`?filter.id=$gt:3&filter.id=$or:$lt:5` where column `id` is greater than `3` **or** less than `5`
2023-02-09 08:21:09 +00:00
2023-02-09 08:25:21 +00:00
`?filter.id=$gt:3&filter.id=$and:$lt:5&filter.id=$or:$eq:7` where column `id` is greater than `3` **and** less than `5` **or** equal to `7`
2023-02-09 08:21:09 +00:00
2023-02-09 08:32:34 +00:00
**Note:** The `$and` comparators are not required. The above example is equivalent to:
2023-02-09 08:21:09 +00:00
2023-02-09 08:25:21 +00:00
`?filter.id=$gt:3&filter.id=$lt:5&filter.id=$or:$eq:7`
2023-02-09 08:21:09 +00:00
2023-02-09 08:32:58 +00:00
**Note:** The first comparator on the the first filter is ignored because the filters are grouped by the column name and chained with an `$and` to other filters.
2023-02-09 08:21:09 +00:00
`...&filter.id=5&filter.id=$or:7&filter.name=Milo&...`
is resolved to:
`WHERE ... AND (id = 5 OR id = 7) AND name = 'Milo' AND ...`
2022-11-16 07:58:00 +00:00
## Troubleshooting
The package does not report error reasons in the response bodies. They are instead
reported as `debug` level [logging ](https://docs.nestjs.com/techniques/logger#logger ).
Common errors include missing `sortableColumns` or `filterableColumns` (the latter only affects filtering).