• v3 (latest)
  • Features
  • Response Caching

Response Caching

Response caching is a technique for reducing server load by caching GraphQL query operation results. For incoming GraphQL Query operations with the same variable values, the same response is returned from a cache instead of executed again.

Quick Start

The response cache is a separate package that needs to be installed.

yarn add @graphql-yoga/plugin-response-cache

The following sample setup show as slow field resolver (Query.slow).

Response cache example
import { createYoga, createSchema } from 'graphql-yoga'
import { createServer } from 'node:http'
import { useResponseCache } from '@graphql-yoga/plugin-response-cache'
 
const yoga = createYoga({
  schema: createSchema({
    typeDefs: /* GraphQL */ `
      type Query {
        slow: String
      }
    `,
    resolvers: {
      Query: {
        slow: async () => {
          await new Promise((resolve) => setTimeout(resolve, 5000))
          return 'I am slow.'
        }
      }
    }
  }),
  plugins: [
    useResponseCache({
      // global cache
      session: () => null
    })
  ]
})
 
const server = createServer(yoga)
server.listen(4000, () => {
  console.info('Server is running on http://localhost:4000/graphql')
})

After starting the server we can execute a GraphQL Query operation, that selects the Query.slow field.

Execute slow GraphQL Query Operation with cUrl
curl -X POST -H 'Content-Type: application/json' http://localhost:4000/graphql \
-d '{"query":"{slow}"}' -w '\nTotal time : %{time_total}'

The output will look similar to the following:

Initial Request time
{"data":{"slow":"I am slow."}}
Total time:5.026632

After executing the same curl statement a second time, the duration is significantly lower.

Cached Request time
{"data":{"slow":"I am slow."}}
Total time:0.007571%

Session based caching

If your GraphQL API returns specific data depending on the viewer's session, you can use the session option to cache the response per session. Usually, the session is determined by an HTTP header, e.g. an user id within the encoded access token.

The session function receives a request parameter that is a Request object.

Response Cache configuration based on header
useResponseCache({
  // cache based on the authentication header
  session: (request) => request.headers.get('authentication')
})

Time to Live (TTL)

It is possible to give cached operations a time to live. Either globally, based on schema coordinates or object types.

If a query operation result contains multiple objects of the same or different types, the lowest TTL is picked.

Response Cache configuration with TTL
useResponseCache({
  session: () => null,
  // by default cache all operations for 2 seconds
  ttl: 2_000,
  ttlPerType: {
    // only cache query operations containing User for 500ms
    User: 500
  },
  ttlPerSchemaCoordinate: {
    // cache operations selecting Query.lazy for 10 seconds
    'Query.lazy': 10_000
  }
})

Invalidations via Mutation

When executing a mutation operation the cached query results that contain type entities within the Mutation result will be automatically be invalidated.

GraphQL mutation operation
mutation UpdateUser {
  updateUser(id: 1, newName: "John") {
    __typename
    id
    name
  }
}
GraphQL operation execution result
{
  "data": {
    "updateLaunch": {
      "__typename": "User",
      "id": "1",
      "name": "John"
    }
  }
}

For the given GraphQL operation and execution result all cached query results that contain the type User with the id 1 will be invalidated.

This behavior can be disabled by setting the invalidateViaMutation option to false.

Disabling mutation invalidation
useResponseCache({
  session: (request) => null,
  invalidateViaMutation: false
})

Manual Invalidation

You can invalidate a type or specific instances of a type using the cache invalidation API.

In order to use the API, you need to manually instantiate the cache an pass it to the useResponseCache plugin.

Manual cache construction
import {
  useResponseCache,
  createInMemoryCache
} from '@graphql-yoga/plugin-response-cache'
 
const cache = createInMemoryCache()
 
useResponseCache({
  session: () => null,
  cache
})

Then in your business logic you can call the invalidate method on the cache instance.

Invalidate all GraphQL query results that reference a specific type:

Invalidating a type
cache.invalidate([{ type: 'User' }])

Invalidate all GraphQL query results that reference a specific entity of a type:

Invalidate a specific entity of a type
cache.invalidate([{ type: 'User', id: '1' }])

Invalidate all GraphQL query results multiple entities in a single call.

Invalidate multiple entities
cache.invalidate([
  { type: 'Post', id: '1' },
  { type: 'User', id: '2' }
])

External Cache

By default, the response cache stores all the cached query results in memory.

If you want a cache that is shared between multiple server instances you can use the Redis cache implementation, which is available as a separate package.

💡
The Redis cache currently only works in Node.js environments.
yarn add @envelop/response-cache-redis
Create a custom Redis Cache
import { useResponseCache } from '@graphql-yoga/plugin-response-cache'
import { createRedisCache } from '@envelop/response-cache-redis'
import Redis from 'ioredis'
 
const redis = new Redis({
  host: 'my-redis-db.example.com',
  port: '30652',
  password: '1234567890'
})
 
const redis = new Redis('redis://:1234567890@my-redis-db.example.com:30652')
 
const cache = createRedisCache({ redis })
 
useResponseCache({
  session: () => null,
  cache
})
Last updated on November 15, 2022