Pagination and filters

Pagination, sorting, and filters in listings with PaginationRequest and PaginationDto.

Koala Nest standardizes paginated listings with reusable classes in the application and domain layers.

Centralized constants in src/core/constants/query-params.ts:

typescript
export const QUERY_FILTER_PARAMS = {
  direction: 'asc' as QueryDirectionType,
  page: 0,
  limit: 30,
};

Base class for list requests, with Swagger decorators and @AutoMap():

typescript
export class PaginationRequest {
  @ApiProperty({
    required: false,
    format: 'int32',
    default: QUERY_FILTER_PARAMS.page,
  })
  @AutoMap()
  page?: number = QUERY_FILTER_PARAMS.page;

  @ApiProperty({
    required: false,
    format: 'int32',
    default: QUERY_FILTER_PARAMS.limit,
  })
  @AutoMap()
  limit?: number = QUERY_FILTER_PARAMS.limit;

  @ApiProperty({ required: false })
  @AutoMap()
  orderBy?: string;

  @ApiProperty({
    required: false,
    enum: ['asc', 'desc'],
    default: QUERY_FILTER_PARAMS.direction,
  })
  @AutoMap()
  direction?: QueryDirectionType = QUERY_FILTER_PARAMS.direction;
}

Extend PaginationRequest to add filters:

typescript
export class ReadManyPersonRequest extends PaginationRequest {
  @ApiProperty()
  @AutoMap()
  name?: string;
}

Query param validation reuses LIST_QUERY_SCHEMA:

typescript
export const LIST_QUERY_SCHEMA = z.object({
  page: z.coerce
    .number()
    .transform((value) => {
      if (value) {
        return value - 1;
      }
      return QUERY_FILTER_PARAMS.page;
    })
    .optional(),
  limit: z.coerce.number().default(QUERY_FILTER_PARAMS.limit).optional(),
  orderBy: z.string().optional(),
  direction: z.enum(['asc', 'desc']).default('asc').optional(),
});

The domain DTO encapsulates skip and orderBy logic for TypeORM:

typescript
export class PaginationDto {
  @AutoMap()
  page?: number = 0;

  @AutoMap()
  limit?: number = QUERY_FILTER_PARAMS.limit;

  @AutoMap()
  orderBy?: string = '';

  @AutoMap()
  direction?: QueryDirectionType = 'asc';

  skip() {
    return (this.limit ?? 0) * (this.page ?? QUERY_FILTER_PARAMS.page);
  }

  generateOrderBy() {
    if (this.orderBy) {
      const orderByField = this.orderBy.split('.');
      return orderByField.reduceRight(
        (acc, item, index) => ({
          [item]: index === orderByField.length - 1 ? this.direction : acc,
        }),
        {},
      );
    }

    return undefined;
  }
}
typescript
findMany(query: PersonQueryDto): Promise<ListResponse<Person>> {
  return this.repository
    .findAndCount({
      where: { name: query.name ? Like(`%${query.name}%`) : undefined },
      order: query.generateOrderBy(),
      skip: query.skip(),
      take: query.limit,
    })
    .then(([items, count]) => ({
      items,
      count,
    }));
}
typescript
export interface ListResponse<T> {
  items: T[];
  count: number;
}
http
GET /person?page=1&limit=10&orderBy=name&direction=desc&name=John

Koala Nest

A facilitator for building NestJS APIs with DDD architecture. Code copied into your repository — readable, adaptable, and under your control.

Creator

igordrangel.com.br

Design, back-end, and product strategy.

Quick Commands

Global CLI and scripts in the generated project

  • bun install -g @koalarx/nest
  • kl-nest new
  • kl-nest add cache
  • bun run migration:run # CRUD template
  • kl-nest --help
© 2026 Koala NestBuilt for NestJS developers and AI-assisted workflows.