Nest.js | Prisma Pagination

NestJS is a framework for building efficient, scalable Node.js server-side applications. Prisma is a powerful ORM (Object-Relational Mapping) tool that can be used with NestJS to interact with databases. To enable pagination in a NestJS application using Prisma, you can use the skip and take options in the prisma.query or prisma.mutation methods to limit the number of records returned in a query. For example, to retrieve the second page of a list of users with a page size of 10, you can use the following query:

const users = await prisma.query.users({
  skip: 10,
  take: 10
});

Here’s an example of a simple pagination function that can be used to paginate a list of data using NestJS and Prisma:

import PrismaService from '@providers/prisma/prisma.service';
import { PaginatedResult, PaginateFunction, paginator } from '@providers/prisma/paginator';
import { User, Prisma } from '@prisma/client';

const paginate: PaginateFunction = paginator({ perPage: 10 });

@Injectable()
export default class UserRepository {
    constructor(private prisma: PrismaService) {}

    async findMany({ where, orderBy, page }:{
        where?: Prisma.UserWhereInput,
        orderBy?: Prisma.UserOrderByWithRelationInput,
        page?: number,
    }): Promise<PaginatedResult<User>> {
        return paginate(
            this.prisma.user,
            {
                where,
                orderBy,
            },
            {
                page,
            },
        );
    }
}

Let's see implementation of this paginator in paginator.ts:

export interface PaginatedResult<T> {
  data: T[]
  meta: {
    total: number
    lastPage: number
    currentPage: number
    perPage: number
    prev: number | null
    next: number | null
  }
}

export type PaginateOptions = { page?: number | string, perPage?: number | string }
export type PaginateFunction = <T, K>(model: any, args?: K, options?: PaginateOptions) => Promise<PaginatedResult<T>>

export const paginator = (defaultOptions: PaginateOptions): PaginateFunction => {
  return async (model, args: any = { where: undefined }, options) => {
    const page = Number(options?.page || defaultOptions?.page) || 1;
    const perPage = Number(options?.perPage || defaultOptions?.perPage) || 10;

    const skip = page > 0 ? perPage * (page - 1) : 0;
    const [total, data] = await Promise.all([
      model.count({ where: args.where }),
      model.findMany({
        ...args,
        take: perPage,
        skip,
      }),
    ]);
    const lastPage = Math.ceil(total / perPage);

    return {
      data,
      meta: {
        total,
        lastPage,
        currentPage: page,
        perPage,
        prev: page > 1 ? page - 1 : null,
        next: page < lastPage ? page + 1 : null,
      },
    };
  };
};

Example of result:

{
    "data": [
      {
          "id": 1,
          "firstName": null,
          "lastName": null,
          "dateOfBirth": null,
          "email": "test@gmail.com",
          "zipCode": null,
          "city": null,
          "avatar": null
      }, 
      ...
    ],
    "meta": {
        "total": 5,
        "lastPage": 1,
        "currentPage": 1,
        "perPage": 10,
        "prev": null,
        "next": null
    }
}

If you have any questions or feedback about this article, feel free to leave a comment.