Build Complete REST API Feature with Nest JS (Using Prisma and Postgresql) from Scratch - Beginner-friendly - PART 4
Tags: nestjs, typescript, api, backend
Published: 2023-10-28 20:52:06

## Scope of discussion:
1. Update `getAllPosts` function to have pagination
2. Create a utility function to handle pagination

---

In the third part, we've created `Post` endpoints:
- `POST /posts`: Create a new post,
- `GET /posts`: Get all posts,
- `GET /posts/:id`: Get post by ID,
- `PATCH /posts/:id`: Update post by ID,
- `DELETE /posts/:id`: Delete post by ID.

Let's focus on `get all posts` endpoint. To get the `posts` data, we use prisma ORM `await this.prisma.post.findMany()` and it will return all the `posts` data. Looks fine, right? but it's not actually fine. Imagine if our `posts` data is getting bigger, let's say our `posts` have 100k rows, it will make the API response slow. The more data, the slower it will be.

So, what's the solution?

Fortunately, prisma provide `skip` and `take` property in `findMany` ORM; for example:
```typescript
await this.prisma.post.findMany({
  skip: 0,
  take: 10,
});
```

We'll use `query` parameter in the URL to input the pagination variable `GET /posts?page=1&size=2`. `page` represents the selected page, `size` represents how many data we want to display.

Let's create a new DTO (Data Transfer Object) to validate the query params. Create a new file called `query-pagination.dto.ts`:
```typescript
// src/common/dtos/query-pagination.dto.ts

import { IsNumberString, IsOptional } from 'class-validator';

export class QueryPaginationDto {
  @IsOptional()
  @IsNumberString()
  page?: string;

  @IsOptional()
  @IsNumberString()
  size?: string;
}
```

We'll create a utility code to handle our pagination called `pagination.utils.ts`:
```typescript
// src/common/utils/pagination.utils.ts

import { NotFoundException } from '@nestjs/common';
import { QueryPaginationDto } from '../dtos/query-pagination.dto';

const DEFAULT_PAGE_NUMBER = 1;
const DEFAULT_PAGE_SIZE = 10;

export interface PaginateOutput<T> {
  data: T[];
  meta: {
    total: number;
    lastPage: number;
    currentPage: number;
    totalPerPage: number;
    prevPage: number | null;
    nextPage: number | null;
  };
}

export const paginate = (
  query: QueryPaginationDto,
): { skip: number; take: number } => {
  const size = Math.abs(parseInt(query.size)) || DEFAULT_PAGE_SIZE;
  const page = Math.abs(parseInt(query.page)) || DEFAULT_PAGE_NUMBER;
  return {
    skip: size * (page - 1),
    take: size,
  };
};

export const paginateOutput = <T>(
  data: T[],
  total: number,
  query: QueryPaginationDto,
  //   page: number,
  //   limit: number,
): PaginateOutput<T> => {
  const page = Math.abs(parseInt(query.page)) || DEFAULT_PAGE_NUMBER;
  const size = Math.abs(parseInt(query.size)) || DEFAULT_PAGE_SIZE;

  const lastPage = Math.ceil(total / size);

  // if data is empty, return empty array
  if (!data.length) {
    return {
      data,
      meta: {
        total,
        lastPage,
        currentPage: page,
        totalPerPage: size,
        prevPage: null,
        nextPage: null,
      },
    };
  }

  // if page is greater than last page, throw an error
  if (page > lastPage) {
    throw new NotFoundException(
      `Page ${page} not found. Last page is ${lastPage}`,
    );
  }

  return {
    data,
    meta: {
      total,
      lastPage,
      currentPage: page,
      totalPerPage: size,
      prevPage: page > 1 ? page - 1 : null,
      nextPage: page < lastPage ? page + 1 : null,
    },
  };
};
```
In the code above, we created:
- `const DEFAULT_PAGE_NUMBER` to define the default page number,
- `const DEFAULT_PAGE_SIZE` to define the default page size per page,
- `interface PaginateOutput` to define the response schema we'll receive. The `data` itself contains an array and it is dynamic since we pass generic type `T`, for example:
```typescript
const users: User[] = [/* ... */];
const paginateOutput: PaginateOutput<User> = {
  data: users,
  total: users.length,
  page: 1,
  limit: 10,
};
```
- `function paginate` to convert query params into prisma property. It returns an object with `skip` and `page` properties and we can use it in prisma. We'll use it in `posts.service`
- `function paginateOutput` to handle the pagination output response. It has a generic type and three parameters (data, total, and query).


All set. Now, we're ready to update our `posts.controller` and `posts.service`. Let's start with `posts.service` by updating `getAllPosts` function:
```typescript
async getAllPosts(query?: QueryPaginationDto): Promise<PaginateOutput<Post>> {
  const [posts, total] = await Promise.all([
    await this.prisma.post.findMany({
      ...paginate(query),
    }),
    await this.prisma.post.count(),
  ]);

  return paginateOutput<Post>(posts, total, query);
}
```

In the `getAllPosts` function above, we add a `QueryPaginationDto` as an optional parameter called `query`. We also updated the function return type to `Promise<PaginateOutput<Post>>` since we want the response to be a pagination. 

The tricky part is on the `Promise.all`. Besides we need the data, we need the total data or count data as well, that's why also invoke `await this.prisma.post.count()`. `Posts` data will be stored in `posts` variable, and total data will be stored in `total` using `Promise.all`.

Then, we return the data using `paginateOutput` and pass parameters into it `paginateOutput<Post>(posts, total, query);`.

We're done with `posts.service`. Now let's move on to `posts.controller`.

In the `posts.controller`, we'll only update `getAllPosts` function:
```typescript
getAllPosts(
  @Query() query?: QueryPaginationDto,
): Promise<PaginateOutput<CPost>> {
  return this.postsService.getAllPosts(query);
}
```
It's more simpler compared to `posts.service`. What we need to do is just add `@Query` decorator and pass it into the `getAllPosts` service.

So we're done with the pagination.

Let's test the pagination:

![Pagination post](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/83hjpuk7pj88gm4yiaqz.png)

Great! All works as we expect 🔥

The full code of part 4 can be accessed here: https://github.com/alfism1/nestjs-api/tree/part-four

Moving on to part 5:
https://dev.to/alfism1/build-complete-rest-api-feature-with-nest-js-using-prisma-and-postgresql-from-scratch-beginner-friendly-part-5-1ggd