GraphQL query best practices

Operation naming, GraphQL variables, and more


When creating queries and mutations, follow these best practices to get the most out of both GraphQL and Apollo tooling.

Name all operations

These two queries fetch the same data:

GraphQL
1# Recommended ✅
2query GetBooks {
3  books {
4    title
5  }
6}
7
8# Not recommended ❌
9query {
10  books {
11    title
12  }
13}

The first query is named GetBooks. The second query is anonymous.

You should define a name for every GraphQL operation in your application. Doing so provides the following benefits:

  • You clarify the purpose of each operation for both yourself and your teammates.

  • You avoid unexpected errors when combining multiple operations in a single query document (an anonymous operation can only appear alone).

  • You improve debugging output in both client and server code, helping you identify exactly which operation is causing issues.

  • Apollo GraphOS Studio provides helpful operation-level metrics, which require named operations.

Use GraphQL variables to provide arguments

These two queries can both fetch a Dog object with ID "5":

GraphQL
1# Recommended ✅
2query GetDog($dogId: ID!) {
3  dog(id: $dogId) {
4    name
5    breed
6  }
7}
8
9# Not recommended ❌
10query GetDog {
11  dog(id: "5") {
12    name
13    breed
14  }
15}

The first query uses a variable ($dogId) for the value of the dog field's required argument. This means you can use the query to fetch a Dog object with any ID, making it much more reusable.

You pass variable values to useQuery (or useMutation) like so:

JavaScript
dog.tsx
1const GET_DOG = gql`
2  query GetDog($dogId: ID!) {
3    dog(id: $dogId) {
4      name
5      breed
6    }
7  }
8`;
9
10function Dog({ id }) {
11  const { loading, error, data } = useQuery(GET_DOG, {
12    variables: {
13      dogId: id
14    },
15  });
16  // ...render component...
17}

Disadvantages of hardcoded GraphQL arguments

Beyond reusability, hardcoded arguments have other disadvantages relative to variables:

Reduced cache effectiveness

If two otherwise identical queries have different hardcoded argument values, they're considered entirely different operations by your GraphQL server's cache. The cache enables your server to skip parsing and validating operations that it's encountered before, improving performance.

The server-side cache also powers features like automatic persisted queries and query plans in a federated gateway. Hardcoded arguments reduce the performance gains of these features and take up useful space in the cache.

Reduced information privacy

The value of a GraphQL argument might include sensitive information, such as an access token or a user's personal info. If this information is included in a query string, it's cached with the rest of that query string.

Variable values are not included in query strings. You can also specify which variable values (if any) are included in metrics reporting to Studio.

Query only the data you need, where you need it

One of GraphQL's biggest advantages over a traditional REST API is its support for declarative data fetching. Each component can (and should) query exactly the fields it requires to render, with no superfluous data sent over the network.

If instead your root component executes a single, enormous query to obtain data for all of its children, it might query on behalf of components that aren't even rendered given the current state. This can result in a delayed response, and it drastically reduces the likelihood that the query's result can be reused by a server-side response cache.

In the large majority of cases, a query such as the following should be divided into multiple queries that are distributed among the appropriate components:

Click to expand
GraphQL
1# Not recommended ❌
2query GetGlobalStatus {
3  stores {
4    id
5    name
6    address {
7      street
8      city
9    }
10    employees {
11      id
12    }
13    manager {
14      id
15    }
16  }
17  products {
18    id
19    name
20    price {
21      amount
22      currency
23    }
24  }
25  employees {
26    id
27    role
28    name {
29      firstName
30      lastName
31    }
32    store {
33      id
34    }
35  }
36  offers {
37    id
38    products {
39      id
40    }
41    discount {
42      discountType
43      amount
44    }
45  }
46}
  • If you have collections of components that are always rendered together, you can use fragments to distribute the structure of a single query between them. See Colocating fragments.

  • If you're querying a list field that returns more items than your component needs to render, you should paginate that field.

Query global data and user-specific data separately

Some fields return the exact same data regardless of which user queries them:

GraphQL
1# Returns all elements of the periodic table
2query GetAllElements {
3  elements {
4    atomicNumber
5    name
6    symbol
7  }
8}

Other fields return different data depending on which user queries them:

GraphQL
1# Returns the current user's documents
2query GetMyDocuments {
3  myDocuments {
4    id
5    title
6    url
7    updatedAt
8  }
9}

To improve the performance of your server-side response cache, fetch these two types of fields in separate queries whenever possible. By doing so, your server can cache just a single response for a query like GetAllElements above, while caching separate responses for each user that executes GetMyDocuments.

Set your app's name and version for metrics reporting (paid)

This recommendation is most pertinent to Studio organizations with a paid plan, however it can be helpful for all apps.

The constructor of ApolloClient accepts the name and version options:

JavaScript
1const client = new ApolloClient({
2  uri: 'http://localhost:4000/graphql',
3  cache: new InMemoryCache(),
4  name: 'MarketingSite',
5  version: '1.2'
6});

If you specify these values, Apollo Client automatically adds them to each operation request as HTTP headers (apollographql-client-name and apollographql-client-version).

Then if you've configured metrics reporting in Studio, Apollo Server includes your app's name and version in the operation traces it reports to Studio. This enables you to segment metrics by client.

Feedback

Edit on GitHub

Forums