Skip to main content

Paginated lists

Vendure's list queries follow a set pattern which allows for pagination, filtering & sorting. This guide will demonstrate how to implement your own paginated list queries.

API definition​

Let's start with defining the GraphQL schema for our query. In this example, we'll image that we have defined a custom entity to represent a ProductReview. We want to be able to query a list of reviews in the Admin API. Here's how the schema definition would look:

src/plugins/reviews/api/api-extensions.ts
import gql from 'graphql-tag';

export const adminApiExtensions = gql`
type ProductReview implements Node {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
product: Product!
productId: ID!
text: String!
rating: Float!
}

type ProductReviewList implements PaginatedList {
items: [ProductReview!]!
totalItems: Int!
}

# Generated at run-time by Vendure
input ProductReviewListOptions

extend type Query {
productReviews(options: ProductReviewListOptions): ProductReviewList!
}
`;

Note that we need to follow these conventions:

  • The type must implement the Node interface, i.e. it must have an id: ID! field.
  • The list type must be named <EntityName>List and must implement the PaginatedList interface.
  • The list options input type must be named <EntityName>ListOptions.

Given this schema, at runtime Vendure will automatically generate the ProductReviewListOptions input type, including all the filtering & sorting fields. This means that we don't need to define the input type ourselves.

Resolver​

Next, we need to define the resolver for the query.

src/plugins/reviews/api/admin.resolver.ts
import { Args, Query, Resolver } from '@nestjs/graphql';
import { Ctx, PaginatedList, RequestContext } from '@vendure/core';

import { ProductReview } from '../entities/product-review.entity';
import { ProductReviewService } from '../services/product-review.service';

@Resolver()
export class ProductReviewAdminResolver {
constructor(private productReviewService: ProductReviewService) {}

@Query()
async productReviews(
@Ctx() ctx: RequestContext,
@Args() args: any,
): Promise<PaginatedList<ProductReview>> {
return this.productReviewService.findAll(ctx, args.options || undefined);
}
}

Service​

Finally, we need to implement the findAll() method on the ProductReviewService. Here we will use the ListQueryBuilder to build the list query. The ListQueryBuilder will take care of

src/plugins/reviews/services/product-review.service.ts
import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/typeorm';
import { ListQueryBuilder, ListQueryOptions, PaginatedList, RequestContext } from '@vendure/core';

import { ProductReview } from '../entities/product-review.entity';

@Injectable()
export class ProductReviewService {
constructor(
private listQueryBuilder: ListQueryBuilder,
) {}

findAll(ctx: RequestContext, options?: ListQueryOptions<ProductReview>): Promise<PaginatedList<ProductReview>> {
return this.listQueryBuilder
.build(ProductReview, options, { relations: ['product'], ctx })
.getManyAndCount()
.then(([items, totalItems]) => ({ items, totalItems }));
}
}

Usage​

Given the above parts of the plugin, we can now query the list of reviews in the Admin API:

query {
productReviews(
options: {
skip: 0
take: 10
sort: {
createdAt: DESC
}
filter: {
rating: {
between: { start: 3, end: 5 }
}
}
) {
totalItems
items {
id
createdAt
product {
name
}
text
rating
}
}
}

In the above example, we are querying the first 10 reviews, sorted by createdAt in descending order, and filtered to only include reviews with a rating between 3 and 5.