Appearance
Pagination
Pagination is a concept where large datasets will be requested by the client in smaller chunks. The most common concepts we use are called Offset pagination and Keyset pagination.
To explain both concepts we will be using following example:
- A webshop that has thousands different products wants to have a simple index endpoint that will return their products.
- Our domain will be called
productsand we will refer to its entity asproduct
Shared classes
For both Keyset and Offset pagination we will use SearchQuery. In this abstract class we will define some common properties that always will be available in pagination.
typescript
export enum SortDirection {
ASC = "asc",
DESC = "desc",
}
export abstract class SortQuery {
abstract key: unknown;
abstract order: SortDirection;
}
export abstract class FilterQuery {
[key: string]: FilterQuery | string | string[] | undefined;
}
export abstract class SearchQuery {
abstract sort?: SortQuery[];
abstract filter?: FilterQuery;
abstract search?: string;
}SortDirectionwill be used to indicate how we will order our data when fetching it from the databaseSortQueryindicates whichkeywill be used and whatorderwe will use.FilterQuerydefines a property of our entity.- This either can be a search value of type
string | string[] - Or can be nested because of the
FilterQuerytype - When the value is
undefinedit will be ignored
- This either can be a search value of type
SearchQueryis a class that brings these classes together and will be used to extend our own query class
Offset pagination
In the case of offset pagination we will be splitting our dataset into different pages. We will be using following classes to indicate we want to create a offset pagination query.
PaginatedOffsetQuery
- This class is the main pagination part of our query.
limitindicates how many items we will be fetching from the database.offsetdefines how many items we will skip in our database query.
typescript
import { Type } from "class-transformer";
import {
IsInt,
IsOptional,
IsPositive,
Max,
Min,
ValidateNested,
} from "class-validator";
import { ApiProperty } from "@nestjs/swagger";
import { SearchQuery } from "../../query/search.query.js";
export class PaginatedOffsetQuery {
@ApiProperty({ minimum: 1, maximum: 100 })
@Type(() => Number)
@Max(100)
@IsPositive()
@IsInt()
limit: number;
@ApiProperty({ minimum: 0 })
@Type(() => Number)
@Min(0)
@IsInt()
offset: number;
}
export abstract class PaginatedOffsetSearchQuery extends SearchQuery {
@ApiProperty({ type: PaginatedOffsetQuery, required: false })
@IsOptional()
@Type(() => PaginatedOffsetQuery)
@ValidateNested()
pagination?: PaginatedOffsetQuery;
}ViewProductsQuery
- This is our implementation of Offset pagination. The client will use this class to query products for the index endpoint.
typescript
export class ViewProductsQuery extends PaginatedOffsetSearchQuery {
@ApiProperty({ type: ViewProductsSortQuery, required: false, isArray: true })
@IsOptional()
@Type(() => ViewProductsSortQuery)
@ValidateNested()
sort?: ViewProductsSortQuery[];
@ApiProperty({ type: ViewProductsFilterQuery })
@IsOptional()
@Type(() => ViewProductsFilterQuery)
@ValidateNested()
filters?: ViewProductsFilterQuery;
@ApiProperty({ type: String, required: false })
@IsOptional()
search?: string;
}