DEV Community

WJH
WJH

Posted on

GraphQL Design Pattern (1): Pagination & Summary

So I designed a schema for a common feature,
basically frontend needs to display two things:

  1. Summary of items
  2. The items

Suppose the page is about showing a list of
animals, and we want to show them the summary of
each type of animals.

FILTERS
[ ] Mammal (200)
[x] Birds (110)
[ ] Fish (200)

ITEMS
- Eagle
- Pigeon
- Stork
- ...
Enter fullscreen mode Exit fullscreen mode

Initial Design

Initially, the schema I designed is as follows:

query {
  displayAnimals(animalTypes: [AnimalType!]!): DisplayAnimalsResult!
}

enum AnimalType {
  MAMMAL
  BIRD
  FISH
}

type DisplayAnimalsResult {
  summary: DisplayAnimalsResultSummary!
  animals: FilteredAnimals!
}

type DisplayAnimalsResultSummary {
  mammal: Int!
  bird: Int!
  fish: Int!
}

type FilteredAnimals {
  totalCount: Int!  
  paginate(pagingOptions: PagingOptions!): [Animal!]!
}

type PagingOptions {
  currentPage: Int!
  itemsPerPage: Int!
}

type Animal {
  name: String!
}
Enter fullscreen mode Exit fullscreen mode

And the frontend query:

query DisplayAnimal(
  $animalTypes: [AnimalType!]!
  $pagingOptions: PagingOptions!
) {
  displayAnimals(animalTypes: $animalTypes) {
    summary {
      mammal
      bird
      fish
    }
    animals {
      totalCount
      paginate(pagingOptions: $pagingOptions) {
        name
      }
    } 
  }
}
Enter fullscreen mode Exit fullscreen mode

The Problem

  1. The summary is hardcorded. If frontend needed a new kind of summary, the schema has to change.
  2. The summary property feels like a boilerplate to implement.

A Better Design

After seeing this pattern over and over and getting
frustrated, I suddenly realised that the summary
property is actually not needed!

Let's see the new schema (note that DisplayAnimalsResult and DisplayAnimalsResultSummary are gone):

query {
  displayAnimals(animalTypes: [AnimalType!]!): FilteredAnimals!
}

enum AnimalType {
  MAMMAL
  BIRD
  FISH
}

type FilteredAnimals {
  totalCount: Int!  
  paginate(pagingOptions: PagingOptions!): [Animal!]!
}

type PagingOptions {
  currentPage: Int!
  itemsPerPage: Int!
}

type Animal {
  name: String!
}
Enter fullscreen mode Exit fullscreen mode

And the frontend query (here's the magic, using
GraphQL Property Alias):

query DisplayAnimal(
  $animalTypes: [AnimalType!]!
  $pagingOptions: PagingOptions!
) {
  mammal: displayAnimals(animalTypes: [MAMMAL]) {
    totalCount
  }
  bird: displayAnimals(animalTypes: [BIRD]) {
    totalCount
  }
  fish: displayAnimals(animalTypes: [FISH]) {
    totalCount
  }
  displayAnimals(animalTypes: $animalTypes) {
    animals {
      totalCount
      paginate(pagingOptions: $pagingOptions) {
        name
      }
    } 
  }
}
Enter fullscreen mode Exit fullscreen mode

How is this better?

  1. No need to implement summary resolver on backend, leading to lesser code.
  2. The summary is no longer hardcoded, e.g. you can query total count of birds and fishes.
  3. Smaller schema, less noise.

Conclusion

GraphQL Alias is a powerful feature, thus remember to incorporate it when designing the schema.

P/S

Actually I wrote this for my workplace team. I decided to post it here because maybe others might find it helpful.

Discussion (0)