Search code examples
postgresqlgraphqltypeormapollo-servertypegraphql

How to paginate searched results?


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,
        };
    }

Solution

  • 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,
            };
        }