No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Convierte tus certificados en títulos universitarios en USA

Antes: $249

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

18 Días
22 Hrs
18 Min
49 Seg

Agregando paginación

15/24
Recursos

Una base de datos puede tener miles y miles de registros, los cuales conviene consultar de forma gradual y en partes para que sea más ameno para el usuario que consume la información.

Cómo es la paginación en MongoDB

Las consultas que realices en MongoDB permiten separar los resultados en partes iguales y desarrollar en el front-end la típica lógica de paginación de resultados.

Paso 1: tipado de datos

Comienza creando un DTO para el tipado de los datos que construirán la paginación.

// products/products.dto.ts

import { IsOptional, Min } from 'class-validator';
export class FilterProductsDto {

  @IsOptional()
  @IsPositive()
  limit: number;        // Cantidad de registros por página

  @IsOptional()
  @Min(0)
  offset: number;      // Número de registros a ignorar
}

Paso 2: aplicar “limit” y “offset” en el servicio

El servicio de lectura de los registros recibe los parámetros para crear el paginador y utilizarlos en la consulta.

// products/products.service.ts
import { FilterProductsDto } from './products.dtos';

@Injectable()
export class ProductsService {

  findAll(params?: FilterProductsDto) { 
    if (params) {
      const { limit, offset } = params;
      return this.productModel.find().skip(offset).limit(limit).exec();
    }
    return this.productModel.find().exec();
  }
}

Paso 3: endpoint paginador de registros

El controlador será el encargado de recibir estos datos y pasárselos al servicio para devolver los datos paginados.

// products/products.controller.ts
import { FilterProductsDto } from '../dtos/products.dtos';

@Controller('products')
export class ProductsController {

  @Get()
  getProducts(@Query() params: FilterProductsDto) {
    return this.productsService.findAll(params);
  }
}

Los parámetros que construyen un paginador suelen recibirse por medio de Query Params y estos deben ser opcionales. El backend tiene que contemplar valores por defecto en el caso de que el front-end no envíe nada y el endpoint debe continuar funcionando correctamente.

Paso 4: configuración de Query Params

Por defecto, todos los Query Params son del tipo String. NestJS nos ayuda a convertirlos a números enteros con la siguiente configuración en el archivo main.ts.

// src/main.ts
new ValidationPipe({
  transformOptions: {
    enableImplicitConversion: true,    // Convertir Query Params a números entero
  }
})

De esta manera, tu endpoint del tipo GET se encuentra listo para permitirle al front-end crear un paginador y facilitar la lectura de resultados a los usuarios.

Contribución creada por: Kevin Fiorentino.


Código de ejemplo para agregar paginación

// src/products/dtos/products.dtos.ts

import {
 ...
  IsOptional, // 👈 new decorator
  Min,  // 👈 new decorator
} from 'class-validator';

...

export class FilterProductsDto { // 👈 new DTO
  @IsOptional()
  @IsPositive()
  limit: number;

  @IsOptional()
  @Min(0)
  offset: number;
}
// src/products/services/products.service.ts

import {
  CreateProductDto,
  UpdateProductDto,
  FilterProductsDto,  // 👈 import DTO
} from './../dtos/products.dtos';

@Injectable()
export class ProductsService {

  findAll(params?: FilterProductsDto) { // 👈 
    if (params) {
      const { limit, offset } = params;
      return this.productModel.find().skip(offset).limit(limit).exec();  // 👈
    }
    return this.productModel.find().exec();
  }
  
}
// src/products/controllers/products.controller.ts

import { ..., FilterProductsDto } from '../dtos/products.dtos'; // 👈 import DTO

@Controller('products')
export class ProductsController {
  ...

  @Get()
  @ApiOperation({ summary: 'List of products' })
  getProducts(@Query() params: FilterProductsDto) { // 👈
    return this.productsService.findAll(params);
  }
  
  ...

}
// src/main.ts
   new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),

Aportes 11

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Buenas Nicolás, el .skip() del método find() no funciona adecuadamente, deberia ser .skip( offset * limit), para que la paginación funcione correctamente.
Quedando así la función:

async findAll(params?: FilterDto) {
    if (params) {
      const { limit, offset } = params;
      return await this.dataModel
        .find()
        .skip(offset * limit)
        .limit(limit)
        .exec();
    }
    return await this.dataModel.find().exec();
  }

Hola 👋
Yo le eh agregado algo mas te logica para poder obtener el total de recursos que nuestra db tiene y optimizandolo con un Promise.all para no bloquear el hilo y que sea como un “paralelismo”.

async findMany(params?: FilterProductstDto) {
    const { limit = 5, offset = 0 } = params;

    const [total, products] = await Promise.all([
      this.productModel.countDocuments(),
      this.productModel
        .find()
        .skip(offset * limit)
        .limit(limit)
        .exec(),
    ]);

    return { total, products };
  }


Espero haberles aportado algo 🚀

  async findAll(params?: FilterProductsDto): Promise<Product[]> {
    const { limit = 5, offset = 0 } = params;
    return await this.productModel
      .find()
      .skip(offset * limit)
      .limit(limit)
      .exec();
  }
Para generar productos de manera random, podemos hacerlo así: ```js { "name": "{{$randomProductName}}", "description": "{{$randomPhrase}}", "price": {{$randomPrice}}, "stock": {{$randomInt}}, "image": "{{$randomUrl}}" } ```
Si quieren generar productos en Postman de manera random, para poblar tu documento y probar la paginación, puedes hacer uso de los valores random, así: ```js { "name": "{{$randomProductName}}", "description": "{{$randomPhrase}}", "price": {{$randomPrice}}, "stock": {{$randomPrice}}, "image": "{{$randomUrl}}" } ```
Dejo mi paginador para un resultado más detallado como el siguiente ejemplo: ![](https://static.platzi.com/media/user_upload/image-e04a24dc-b62b-4557-9af3-cd8a55796545.jpg) Código: ![](https://static.platzi.com/media/user_upload/image-4a375c6f-4c6a-4cb1-91d4-0ff064a5782e.jpg)
```ts async findAll(params: FilterProductsDTO): Promise<PaginationProduct> { const { limit, page } = params; const [quantityProducts, products] = await Promise.all([ this.productModel.countDocuments().exec(), this.productModel.find().skip(page * limit).limit(limit).exec() ]); const totalPages = (Math.ceil((quantityProducts / limit)) - 1); const quantityPerPage = products.length; if (params.page > totalPages) throw new NotFoundException(`Máximo de paginas: ${totalPages}`); return { page: { currentPage: params.page, lastPage: params.page == totalPages, totalPages: totalPages, totalElements: quantityProducts, size: quantityPerPage }, products } } ```async findAll(*params*: FilterProductsDTO): Promise\<PaginationProduct> {    const { limit, page } = *params*;    const \[quantityProducts, products] = *await* Promise.all(\[        this.productModel.countDocuments().exec(),        this.productModel.find().skip(page \* limit).limit(limit).exec()    ]);        const totalPages = (Math.ceil((quantityProducts / limit)) - 1);    const quantityPerPage = products.length;     *if* (*params*.page > totalPages) *throw* new NotFoundException(`Máximo de paginas: ${totalPages}`);     *return* {        page: {            currentPage: *params*.page,            lastPage: *params*.page == totalPages,            totalPages: totalPages,            totalElements: quantityProducts,            size: quantityPerPage        },        products    }}
Dejo mi páginador por si quieres un resultado más detallado: ![](https://static.platzi.com/media/user_upload/image-1cbf78b8-a8a6-45ac-9f80-ba764b085619.jpg) ```ts async findAll(params: FilterProductsDTO): Promise<PaginationProduct> { const { limit, page } = params; const [quantityProducts, products] = await Promise.all([ this.productModel.countDocuments().exec(), this.productModel.find().skip(page * limit).limit(limit).exec() ]); const totalPages = (Math.ceil((quantityProducts / limit)) - 1); const quantityPerPage = products.length; if (params.page > totalPages) throw new NotFoundException(`Máximo de paginas: ${totalPages}`); return { page: { currentPage: params.page, lastPage: params.page == totalPages, totalPages: totalPages, totalElements: quantityProducts, size: quantityPerPage }, products } } ```
Dejo mi paginador por si quieres un resultado más detallado como este: ![](https://static.platzi.com/media/user_upload/image-667860f3-c3cb-4b78-bbab-ff2c4746ae8d.jpg) ```js async findAll(params: FilterProductsDTO): Promise<PaginationProduct> { const { limit, page } = params; const [quantityProducts, products] = await Promise.all([ this.productModel.countDocuments().exec(), this.productModel.find().skip(page * limit).limit(limit).exec() ]); const totalPages = (Math.ceil((quantityProducts / limit)) - 1); const quantityPerPage = products.length; if (params.page > totalPages) throw new NotFoundException(`Máximo de paginas: ${totalPages}`); return { page: { currentPage: params.page, lastPage: params.page == totalPages, totalPages: totalPages, totalElements: quantityProducts, size: quantityPerPage }, products } } ```La interfaz de respuesta que uso de respuesta es: ```js import { Product } from "./product.entity"; export interface PaginationProduct{ products: Product[], page: Page } interface Page{ currentPage: string | number, totalElements: number, totalPages:number, lastPage: boolean, size: number } ```*import* { Product } *from* "./product.entity"; *export* interface PaginationProduct{        products: Product\[],    page: Page} interface Page{    currentPage: string | number,        totalElements: number,    totalPages:number,    lastPage: boolean,    size: number}

Hola…
Cuando estaba haciendo esto, tuve un problema en la consola:

TypeError: classTransformer.plainToClass is not a function
    at ValidationPipe.transform 

Al parecer es un problema con la librería class-validator.
En el curso se especifica en el poackage.json de esta manera:

"class-transformer": "^0.4.0",

para evitar el error, debe hacerse downgrade:

"class-transformer": "0.4.0",

Asi quedo mi servicio de obtener paginas

async getAllPages( params? : FilterPagesDTO ) {

    if( params ){

      const doc = await this.pageModel
        .find({}, { "config": 1 })
        .skip( params.offset )
        .limit( params.limit )

      return doc;

    }

    const doc = await this.pageModel.find({}, { "config": 1 })

    return doc;

  }

y mi DTO

export class FilterPagesDTO {
    
    @IsOptional()
    @IsPositive()    
    limit: number
    
    @IsOptional()
    @IsPositive()
    @Min(0)    
    offset: number

}