- Notifications
You must be signed in to change notification settings - Fork0
License
GlobalArtInc/nestjs-typeorm-pagination
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Pagination and filtering helper method for TypeORM repositories or query builders usingNest.js framework.
- Pagination conforms toJSON:API
- Sort by multiple columns
- Search across columns
- Select columns
- Filter using operators (
$eq
,$not
,$null
,$in
,$gt
,$gte
,$lt
,$lte
,$btw
,$ilike
,$sw
,$contains
) - Include relations and nested relations
- Virtual column support
npm install nestjs-paginate
The following code exposes a route that can be utilized like so:
http://localhost:3000/cats?limit=5&page=2&sortBy=color:DESC&search=i&filter.age=$gte:3&select=id,name,color,age
{"data": [ {"id":4,"name":"George","color":"white","age":3 }, {"id":5,"name":"Leche","color":"white","age":6 }, {"id":2,"name":"Garfield","color":"ginger","age":4 }, {"id":1,"name":"Milo","color":"brown","age":5 }, {"id":3,"name":"Kitty","color":"black","age":3 } ],"meta": {"itemsPerPage":5,"totalItems":12,"currentPage":2,"totalPages":3,"sortBy": [["color","DESC"]],"search":"i","filter": {"age":"$gte:3" } },"links": {"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" }}
import{Controller,Injectable,Get}from'@nestjs/common'import{InjectRepository}from'@nestjs/typeorm'import{FilterOperator,FilterSuffix,Paginate,PaginateQuery,paginate,Paginated}from'nestjs-paginate'import{Repository,Entity,PrimaryGeneratedColumn,Column}from'typeorm'@Entity()exportclassCatEntity{ @PrimaryGeneratedColumn()id:number @Column('text')name:string @Column('text')color:string @Column('int')age:number @Column({nullable:true})lastVetVisit:Date|null @CreateDateColumn()createdAt:string}@Injectable()exportclassCatsService{constructor( @InjectRepository(CatEntity)privatereadonlycatsRepository:Repository<CatEntity>){}publicfindAll(query:PaginateQuery):Promise<Paginated<CatEntity>>{returnpaginate(query,this.catsRepository,{sortableColumns:['id','name','color','age'],nullSort:'last',defaultSortBy:[['id','DESC']],searchableColumns:['name','color','age'],select:['id','name','color','age','lastVetVisit'],filterableColumns:{name:[FilterOperator.EQ,FilterSuffix.NOT],age:true,},})}}@Controller('cats')exportclassCatsController{constructor(privatereadonlycatsService:CatsService){} @Get()publicfindAll(@Paginate()query:PaginateQuery):Promise<Paginated<CatEntity>>{returnthis.catsService.findAll(query)}}
constpaginateConfig:PaginateConfig<CatEntity>{/** * Required: true (must have a minimum of one column) * Type: (keyof CatEntity)[] * Description: These are the columns that are valid to be sorted by. */sortableColumns:['id','name','color'],/** * Required: false * Type: 'first' | 'last' * Description: Define whether to put null values at the beginning * or end of the result set. */nullSort:'last',/** * Required: false * Type: [keyof CatEntity, 'ASC' | 'DESC'][] * Default: [[sortableColumns[0], 'ASC]] * Description: The order to display the sorted entities. */defaultSortBy:[['name','DESC']],/** * Required: false * Type: (keyof CatEntity)[] * Description: These columns will be searched through when using the search query * param. Limit search scope further by using `searchBy` query param. */searchableColumns:['name','color'],/** * Required: false * Type: (keyof CatEntity)[] * Default: None * Description: TypeORM partial selection. Limit selection further by using `select` query param. * https://typeorm.io/select-query-builder#partial-selection * Note: You must include the primary key in the selection. */select:['id','name','color'],/** * Required: false * Type: number * Default: 100 * Description: The maximum amount of entities to return per page. * Set it to 0, in conjunction with limit=0 on query param, to disable pagination. */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 */where:{color:'ginger'},/** * Required: false * Type: { [key in CatEntity]?: FilterOperator[] } - Operators based on TypeORM find operators * Default: None * https://typeorm.io/#/find-options/advanced-options */filterableColumns:{age:[FilterOperator.EQ,FilterOperator.IN]},/** * Required: false * Type: RelationColumn<CatEntity> * Description: Indicates what relations of entity should be loaded. */relations:[],/** * Required: false * Type: boolean * Default: false * Description: Load eager relations using TypeORM's eager property. * Only works if `relations` is not defined. */loadEagerRelations:true,/** * 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,/** * Required: false * Type: string * Description: Allow user to choose between limit/offset and take/skip. * Default: PaginationType.TAKE_AND_SKIP * * However, using limit/offset can cause problems with relations. */paginationType:PaginationType.LIMIT_AND_OFFSET,/** * 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',}
You can paginate custom queries by passing on the query builder:
constqueryBuilder=repo.createQueryBuilder('cats').leftJoinAndSelect('cats.owner','owner').where('cats.owner = :ownerId',{ ownerId})constresult=awaitpaginate<CatEntity>(query,queryBuilder,config)
Similar as with repositories, you can utilizerelations
as a simplified left-join form:
http://localhost:3000/cats?filter.toys.name=$in:Mouse,String
constconfig:PaginateConfig<CatEntity>={relations:['toys'],sortableColumns:['id','name','toys.name'],filterableColumns:{'toys.name':[FilterOperator.IN],},}constresult=awaitpaginate<CatEntity>(query,catRepo,config)
Note: Embedded columns on relations have to be wrapped with brackets:
constconfig:PaginateConfig<CatEntity>={sortableColumns:['id','name','toys.(size.height)','toys.(size.width)'],searchableColumns:['name'],relations:['toys'],}
Similar as with relations, you can specify nested relations for sorting, filtering and searching:
http://localhost:3000/cats?filter.home.pillows.color=pink
constconfig:PaginateConfig<CatEntity>={relations:{home:{pillows:true}},sortableColumns:['id','name','home.pillows.color'],searchableColumns:['name','home.pillows.color'],filterableColumns:{'home.pillows.color':[FilterOperator.EQ],},}constresult=awaitpaginate<CatEntity>(query,catRepo,config)
Eager loading should work with TypeORM's eager property out of the box:
@Entity()exportclassCatEntity{// ... @OneToMany(()=>CatToyEntity,(catToy)=>catToy.cat,{eager:true,})toys:CatToyEntity[]}constconfig:PaginateConfig<CatEntity>={loadEagerRelations:true,sortableColumns:['id','name','toys.name'],filterableColumns:{'toys.name':[FilterOperator.IN],},}constresult=awaitpaginate<CatEntity>(query,catRepo,config)
Filter operators must be whitelisted per column inPaginateConfig
.
constconfig:PaginateConfig<CatEntity>={// ...filterableColumns:{// Enable individual operators on a columnid:[FilterOperator.EQ,FilterSuffix.NOT],// Enable all operators on a columnage:true,},}
?filter.name=$eq:Milo
is equivalent with?filter.name=Milo
?filter.age=$btw:4,6
where columnage
is between4
and6
?filter.id=$not:$in:2,5,7
where columnid
isnot2
,5
or7
?filter.summary=$not:$ilike:term
where columnsummary
doesnot containterm
?filter.summary=$sw:term
where columnsummary
starts withterm
?filter.seenAt=$null
where columnseenAt
isNULL
?filter.seenAt=$not:$null
where columnseenAt
isnotNULL
?filter.createdAt=$btw:2022-02-02,2022-02-10
where columncreatedAt
is between the dates2022-02-02
and2022-02-10
?filter.createdAt=$lt:2022-12-20T10:00:00.000Z
where columncreatedAt
is before iso date2022-12-20T10:00:00.000Z
?filter.roles=$contains:moderator
where columnroles
is an array and contains the valuemoderator
?filter.roles=$contains:moderator,admin
where columnroles
is an array and contains the valuesmoderator
andadmin
Multi filters are filters that can be applied to a single column with a comparator.
?filter.createdAt=$gt:2022-02-02&filter.createdAt=$lt:2022-02-10
where columncreatedAt
is after2022-02-02
and before2022-02-10
?filter.id=$contains:moderator&filter.id=$or:$contains:admin
where columnroles
is an array and containsmoderator
oradmin
?filter.id=$gt:3&filter.id=$and:$lt:5&filter.id=$or:$eq:7
where columnid
is greater than3
and less than5
or equal to7
Note: The$and
comparators are not required. The above example is equivalent to:
?filter.id=$gt:3&filter.id=$lt:5&filter.id=$or:$eq:7
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.
...&filter.id=5&filter.id=$or:7&filter.name=Milo&...
is resolved to:
WHERE ... AND (id = 5 OR id = 7) AND name = 'Milo' AND ...
You can use two default decorators @ApiOkResponsePaginated and @ApiPagination to generate swagger documentation for your endpoints
@ApiOkPaginatedResponse
is for response body, return http status is 200
@ApiPaginationQuery
is for query params
@Get() @ApiOkPaginatedResponse(UserDto,USER_PAGINATION_CONFIG,) @ApiPaginationQuery(USER_PAGINATION_CONFIG)asyncfindAll( @Paginate()query:PaginateQuery,):Promise<Paginated<UserEntity>>{}
There is also some syntax sugar for this, and you can use only one decorator@PaginatedSwaggerDocs
for both response body and query params
@Get() @PaginatedSwaggerDocs(UserDto,USER_PAGINATION_CONFIG)asyncfindAll( @Paginate()query:PaginateQuery,):Promise<Paginated<UserEntity>>{}
The package does not report error reasons in the response bodies. They are insteadreported asdebug
levellogging.
Common errors include missingsortableColumns
orfilterableColumns
(the latter only affects filtering).
About
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Releases
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors2
Uh oh!
There was an error while loading.Please reload this page.