How to paginate searched results?

Viewed 1430

How to paginate searched results using cursor based?

this is my function for querying data and there is something wrong because when I load more data, there are items that are not related to the searched query?

    @UseMiddleware(isAuthenticated)
    @Query(() => PaginatedQuizzes)
    async quizzes(
        @Arg('limit', () => Int) limit: number,
        @Arg('cursor', () => String, { nullable: true }) cursor: string | null,
        @Arg('query', () => String, { nullable: true }) query: string | null
    ): Promise<PaginatedQuizzes> {
        const realLimit = Math.min(50, limit);
        const realLimitPlusOne = realLimit + 1;

        const qs = await getConnection()
            .getRepository(Quiz)
            .createQueryBuilder('q')
            .leftJoinAndSelect('q.author', 'author')
            .leftJoinAndSelect('q.questions', 'questions')

        if (query) {
            const formattedQuery = query.trim().replace(/ /g, ' <-> ');

            qs.where(
                `to_tsvector('simple',q.title) @@ to_tsquery('simple', :query)`,
                {
                    query: `${formattedQuery}:*`,
                }
            ).orWhere(
                `to_tsvector('simple',q.description) @@ to_tsquery('simple', :query)`,
                {
                    query: `${formattedQuery}:*`,
                }
            );
        }

        if (cursor) {
            qs.where('q."created_at" < :cursor', {
                cursor: new Date(parseInt(cursor)),
            });
        }

        const quizzes = await qs
            .orderBy('q.created_at', 'DESC')
            .take(realLimitPlusOne)
            .getMany();

        return {
            quizzes: (quizzes as [Quiz]).slice(0, realLimit),
            hasMore: (quizzes as [Quiz]).length === realLimitPlusOne,
        };
    }
3 Answers

You can use a custom system instead of installing a heavy package which can have vulnerabilities.

Here is an example controller with limit and page parameters to get n elements on x page

@Get('elements/:limit/:page')
  getElementsWithPagination(
    @Param('limit') limit: number,
    @Param('page') page: number,
  ): Promise<ElementsEntity[]> {
    return this.elementsService.getElementsWithPagination(limit, page);
  }

Here is the service to access to your repository

async getElementsWithPagination(
    limit: number,
    page: number,
  ): Promise<ElementsEntity[]> {
    return this.elementsRepository.findElementsWithPagination(limit, page);
  }

And finally here is the repository with query arguments, ordered from newest to oldest

async findElementsWithPagination(limit, page): Promise<ElementsEntity[]> {
    return this.find({
      take: limit,
      skip: limit * (page - 1),
      order: { createdAt: 'DESC' },
    });
  }

Thanks to this system, you can query n elements pages by pages.

thank you so much for the responses, but i was able to solved it on my own using find option,

    @UseMiddleware(isAuthenticated)
    @Query(() => PaginatedQuizzes)
    async quizzes(
        @Arg('limit', () => Int) limit: number,
        @Arg('cursor', () => String, { nullable: true }) cursor: string | null,
        @Arg('query', () => String, { nullable: true }) query: string | null
    ): Promise<PaginatedQuizzes> {
        const realLimit = Math.min(20, limit);
        const realLimitPlusOne = realLimit + 1;

        let findOptionInitial: FindManyOptions = {
            relations: [
                'author',
                'questions',
            ],
            order: {
                created_at: 'DESC',
            },
            take: realLimitPlusOne,
        };

        let findOption: FindManyOptions;

        if (cursor && query) {
            const formattedQuery = query.trim().replace(/ /g, ' <-> ');

            findOption = {
                ...findOptionInitial,
                where: [
                    {
                        description: Raw(
                            (description) =>
                                `to_tsvector('simple', ${description}) @@ to_tsquery('simple', :query)`,
                            {
                                query: formattedQuery,
                            }
                        ),
                        created_at: LessThan(new Date(parseInt(cursor))),
                    },
                    {
                        title: Raw(
                            (title) =>
                                `to_tsvector('simple', ${title}) @@ to_tsquery('simple', :query)`,
                            {
                                query: formattedQuery,
                            }
                        ),
                        created_at: LessThan(new Date(parseInt(cursor))),
                    },
                ],
            };
        } else if (cursor) {
            findOption = {
                ...findOptionInitial,
                where: {
                    created_at: LessThan(new Date(parseInt(cursor))),
                },
            };
        } else if (query) {
            const formattedQuery = query.trim().replace(/ /g, ' <-> ');

            findOption = {
                ...findOptionInitial,
                where: [
                    {
                        description: Raw(
                            (description) =>
                                `to_tsvector('simple', ${description}) @@ to_tsquery('simple', :query)`,
                            {
                                query: formattedQuery,
                            }
                        ),
                    },
                    {
                        title: Raw(
                            (title) =>
                                `to_tsvector('simple', ${title}) @@ to_tsquery('simple', :query)`,
                            {
                                query: formattedQuery,
                            }
                        ),
                    },
                ],
            };
        } else {
            findOption = findOptionInitial;
        }

        const quizzes = await Quiz.find(findOption as FindManyOptions);

        return {
            quizzes: (quizzes as [Quiz]).slice(0, realLimit),
            hasMore: (quizzes as [Quiz]).length === realLimitPlusOne,
        };
    }

Hey I've been using this cool package to paginate through a table check it out here

import { buildPaginator } from 'typeorm-cursor-pagination';

const paginator = buildPaginator({
  entity: User,
  paginationKeys: ['id'],
  query: {
    limit: 10,
    order: 'ASC',
  },
});
const { data, cursor } = await paginator.paginate(queryBuilder);
Related