GraphQL Query Efficiency

Using GraphQL offers many benefits but it can be easy to write complex queries that are slow to execute. Here's some best practices to make your GraphQL queries more efficient.

1. Avoid deep nesting

GraphQL allows you to use a single request for nested entities in a single query. Deeply nested queries can result in extensive database joins, or complex data fetching logic — increasing the execution time.

In the Core Administrate GraphQL API we implement a DataLoader to improve the batch loading for nested entities, but there are limitations to what can be optimized. Nesting of connections (where nested edges/node syntax is used) is one of the more expensive operations where multiple database queries will be required.

An example of a complex query might request Learners and their Events. This creates a deeply nested structure fetching Events data for each Learner object:

query nestedLearners {
  learners {
    edges {
      node {
        id
        contact {
          personalName {
            firstName
            lastName
          }
        }
        event {
          id
          title
          classroomStart
          classroomEnd
          sessions {
            edges {
              node {
                id
                code
                title
                timeZonedStart
                timeZonedEnd
              }
            }
          }
          courseTemplate {
            id
            lmsSummary
          }
        }
      }
    }
  }
}

To reduce the complexity, nested queries could be broken down into smaller, more manageable parts. The first query can fetch a list of Learners

query learners {
  learners {
    edges {
      node {
        id
        contact {
          personalName {
            firstName
            lastName
          }
        }
        event {
          id
        }
      }
    }
  }
}

The second one fetches Events separately

query events{
  events(filters: {field: id, operation:in, values: ["eventid1", "eventid2"]}) {
    edges {
      node {
        id
        title
        classroomStart
        classroomEnd
        sessions {
          edges {
            node {
              id
              code
              title
              timeZonedStart
              timeZonedEnd
            }
          }
        }
        courseTemplate {
          id
          lmsSummary
        }
      }
    }
  }
}

2. Avoid over-fetching of fields

One of the main advantages of GraphQL is to avoid over-fetching of information. Over-fetching can lead to increased execution time, as unnecessary data is queried and transferred.

Consider a query that requests all available information about an Event, even when only a few fields are required:

query events {
  events {
    edges {
      node {
        id
        title
        classroomStart
        classroomEnd
        bookedPlaces
        code
        defaultPrice {
          id
        }
        end
        isSoldOut
        learningMode
        displayId
        minPlaces
        maxPlaces
        type
        sessions {
          edges {
            node {
              id
              code
              title
              timeZonedStart
              timeZonedEnd
            }
          }
        }
        courseTemplate {
          id
          lmsSummary
        }
      }
    }
  }
}

This could be rewritten as the smaller query

query eventsShort {
  events {
    edges {
      node {
        id
        title
        sessions {
          edges {
            node {
              id
              code
              title
              timeZonedStart
              timeZonedEnd
            }
          }
        }
      }
    }
  }
}

3. Use aliasing to avoid fetching all custom fields

When Custom fields required to be fetched, there are three ways to do it

Query for all Custom fields:

query allCustomFields {
  events {
    edges {
      node {
        id
        code
        customFieldValues {
          definition{
            key
            label
          }
          value
        }
      }
    }
  }
}

Query for single Custom Field:

query singleCustomField {
  events {
    edges {
      node {
        id
        code
        customFieldValues(filter:{definitionKey: "Q3VzdG9tRmllbGREZWZpbml0aW9uOjI2", }) {
          definition{
            key
            label
          }
          value
        }
      }
    }
  }
}

Query certain Custom Fields using Aliases

query severalCustomFields {
  events {
    edges {
      node {
        id
        code
        customAlias1: customFieldValues(filter:{definitionKey: "Q3VzdG9tRmllbGREZWZpbml0aW9uOjMz"}) {
          definition{
            key
            label
          }
          value
        }
        customAlias2: customFieldValues(filter:{definitionKey: "Q3VzdG9tRmllbGREZWZpbml0aW9uOjM1"}) {
          definition{
            key
            label
          }
          value
        }
      }
    }
  }
}

4. Paginating Results

When fetching a large number of fields, it can be useful to paginate the results to limit the amount of data returned in each response. Pagination could be implemented with first and offset arguments

query eventsShort {
  events(first: 2, offset: 0)  {
    edges {
      node {
        id
        title
      }
    }
  }
}