Response Caching
Response caching is a technique for reducing server load by caching GraphQL query operation results.
Installation
yarn add @graphql-yoga/plugin-response-cache
Quick Start
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: `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')
})
curl -X POST -H 'Content-Type: application/json' http://localhost:4000/graphql \
-d '{"query":"{__typename}"}' -w '\nTotal time : %{time_total}'
This will output something like the following:
{"data":{"slow":"I am slow."}}
Total time:5.026632
After executing the same curl statement a second time, the duration is significantly lower.
{"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.
useResponseCache({
// cache based on the authentication header
session: (request) => request.headers.get('authentication')
})
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 type, the lowest TTL is picked.
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.
mutation {
updateUser(id: 1, newName: "John") {
__typename
id
name
}
}
{
"data": {
"updateLaunch": {
"__typename": "User",
"id": "1",
"name": "John"
}
}
}
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
.
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.
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:
cache.invalidate([{ type: 'User' }])
Invalidate all GraphQL query results that reference a specific entity of a type:
cache.invalidate([{ type: 'User', id: '1' }])
Invalidate all GraphQL query results multiple entities in a single call.
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.
yarn add @envelop/response-cache-redis
import { useResponseCache } from '@graphql-yoga/plugin-response-cache'
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
})