Caching GraphQL query results is surprisingly more about managing staleness than just storing data.

Let’s see it in action. Imagine a simple GraphQL API for a blog.

type Post {
  id: ID!
  title: String!
  content: String!
  author: Author!
  comments: [Comment!]!
}

type Author {
  id: ID!
  name: String!
  posts: [Post!]!
}

type Comment {
  id: ID!
  text: String!
  author: Author!
}

type Query {
  post(id: ID!): Post
  posts: [Post!]!
  author(id: ID!): Author
}

A client might request a specific post and its author:

query GetPostAndAuthor($postId: ID!) {
  post(id: $postId) {
    id
    title
    content
    author {
      id
      name
    }
  }
}

If this query is executed frequently, we can cache the entire JSON response for a given $postId. A simple in-memory cache on the server might store:

{
  "data": {
    "post": {
      "id": "post-123",
      "title": "My First Blog Post",
      "content": "This is the content...",
      "author": {
        "id": "author-abc",
        "name": "Alice"
      }
    }
  }
}

This response can be served directly from the cache for subsequent identical requests, bypassing the database or business logic entirely.

The real power comes from understanding how GraphQL’s field-level resolution interacts with caching. Unlike REST, where you cache an entire endpoint’s response, GraphQL allows for granular caching. If a query requests only the title of a post, only that specific piece of data needs to be cached.

Here’s the mental model:

  1. Request Parsing: The GraphQL server receives a query.
  2. Cache Check: Before executing any resolvers, the server checks if the exact query and its variables are already in the cache.
  3. Cache Hit: If found, the cached JSON response is returned immediately.
  4. Cache Miss: If not found, the server executes the query’s resolvers.
  5. Data Fetching: Resolvers fetch data from underlying sources (databases, other services).
  6. Response Assembly: The server constructs the JSON response.
  7. Cache Population: The newly generated response is stored in the cache, keyed by the query and variables.
  8. Response Return: The response is sent to the client.

The core problem this solves is reducing the load on your backend systems and improving response times for frequently requested data. The "lever" you control is the cache’s Time-To-Live (TTL) and its invalidation strategy.

The surprising part is how deeply you can cache. You’re not just caching the top-level query result. If your caching layer operates at the resolver level, it can cache individual fields. For instance, if the author field for post-123 is requested by multiple different queries, and its data hasn’t changed, that specific author object can be cached and reused across those queries, even if the rest of the post data is different or not cached. This requires a more sophisticated cache keyed by object type, ID, and field name, rather than just the full query string.

The next concept you’ll grapple with is cache invalidation across distributed systems.

Want structured learning?

Take the full Graphql-tools course →