Caching graphql query results with Strapi

Is there any possible way to cache the graphql queries and results in Strapi ?

As there is already a known issue with graphql being very slow with Strapi.

Hi jainsuneet,

Yes there is! The Strapi GraphQL plugin is built using Apollo Server. If you configure the GraphQL plugin settings you can enable caching and automatic persisted queries for Apollo Server. With HTTP based caching and APQ enabled, your client applications (iOS, Android, Web) talking to your GraphQL API can make use of those Apollo features quite easily (the assumption is that you use a Apollo Client library to avoid having to implement the logic around cache control headers, hashing queries, and retrying requests when the registry does not recognize your APQ hash [Apollo has documentation on all of this]).

Do you have any example of such implementation ? It will be really helpful to see a sample application

Hi jainsuneet,

Unfortunately, our implementations are private. I’ve created a small boilerplate for you that should help you get started in your extensions/graphql/config/settings.js file

3 Likes

@jwerle Thank you so much, it worked like a charm. Just one query, is there any way to clear the cache for a particular content type if the content type data is updated ?

Hi @jainsuneet,

Controlling the cache control hint for a particular content type that you’ve created is pretty straightforward. You can define a customer resolver and set the cache hint dynamically in your resolver. I hope this information helps

@jwerle I would like to clear the cache of a particular collection type if the data is updated from strapi admin or by api calls. So I need to somehow clear that particular cache in the collection lifecycle hook (afterUpdate)

Hi @jainsuneet,

Achieving that easily is difficult, but there are work arounds available. I suggest you send in a “nonce” value or something random in your request query variables coming from your client when you want to “bust” the cache. You may want to do this after a mutation. The apollo cache plugin will use values in the query variables for building the cache key.

Hi!
First of all, thanks for the perfect example, this is super helpful and I can’t understand how it’s not a simple config option on the strapi graphql plugin.

However, in case anyone else arrives here and finds themselves in my situation, where you implemented the cache plugin exactly by the book but it just seems to be ignored:
I got this to work only in version 0.9.0 of apollo-server-plugin-response-cache, as the async rewrite of the hooks in newer versions apparently made apollo ignore them (perhaps this is due to strapi using apollo server 2?)

Hope this might save others the time I spent hunting for a solution >_>

1 Like

Hi!
I’m kinda struggling trying to implement cache into my GraphQL queries at server-side.
Right now when we go past 50-100 visitors at the same time, the node server starts struggling handling the GraphQL queries making the website really really slow.
I think what we need is some kind of server-cache for our GraphQL queries, and like a max age of 6 days to refresh or so.
The thing is, we have quite a rare custom implementation (we use strapi for the backend but the website is fully php, we make GraphQL queries through Guzzle plugin, so we’re not using an Apollo client).
I’ve seen Guzzle middleware caches but isn’t this only client based? won’t this still require to query the db for the first time queries of a client? Maybe I got this wrong, but I think what we need is our queries pre-cached on server side…

Ideal scenario: user queries graphql > if it doesn’t find it cached, executes query and server stores cached result // if it finds it cached > result is provided (not querying the bbdd)

Is it possible to configure the strapi graphql server to do this, and not using Apollo clients?
Thanks!

I pass 6 day full time to figure out why it’s not working on strapi v4.

this is the plugins.js file I build to fix the problem (i’am not a js dev sorry for the hacky code)

const MAX_AGE = 3600;
async function sessionId(requestContext) {
    return null;
}
async function shouldReadFromCache(requestContext) {
    return true;
}
async function shouldWriteToCache(requestContext) {
    return true;
}
async function extraCacheKeyData(requestContext) {
}
async function injectCacheControl(requestContext) {
    return {
        requestDidStart(requestContext) {
            return {
                willSendResponse(requestContext) {
                    requestContext.overallCachePolicy.policyIfCacheable.maxAge = "1000";
                    requestContext.overallCachePolicy.policyIfCacheable.scope = "PUBLIC";
                }
            };
        }
    };
}
function setRedisCacheIfEnvSet() {
    if (process.env.REDIS_CACHE_HOST) {
        const { RedisCache } = require('apollo-server-cache-redis');
        const Redis = require('ioredis');
        return new RedisCache({
            client: new Redis({
                host: process.env.REDIS_CACHE_HOST
            }),
        });
    }
    return false;
}
module.exports = ({ env }) => ({
    graphql: {
        config: {
            endpoint: '/graphql',
            shadowCRUD: true,
            depthLimit: 7,
            amountLimit: 2000,
            apolloServer: {
                cache: setRedisCacheIfEnvSet(),
                tracing: false,
                plugins: [
                    apolloServerPluginResponseCache({
                        shouldReadFromCache,
                        shouldWriteToCache,
                        extraCacheKeyData,
                        sessionId,
                    }),
                    injectCacheControl()
                ]
            },
        },
    },
});

1 Like

not working, policyIfCacheable failed when reading for some reason ¯_(ツ)_/¯

{"errors":[{"message":"overallCachePolicy.policyIfCacheable is not a function","extensions":{"code":"INTERNAL_SERVER_ERROR","exception":{"stacktrace":["TypeError: overallCachePolicy.policyIfCacheable is not a function","    at Object.willSendResponse (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/apollo-server-koa/node_modules/apollo-server-core/dist/plugin/cacheControl/index.js:118:66)","    at /home/jchardon/projects/strapy_cloudrun/strapi/node_modules/apollo-server-koa/node_modules/apollo-server-core/dist/utils/dispatcher.js:12:31","    at Array.map (<anonymous>)","    at Dispatcher.callTargets (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/apollo-server-koa/node_modules/apollo-server-core/dist/utils/dispatcher.js:9:29)","    at Dispatcher.invokeHook (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/apollo-server-koa/node_modules/apollo-server-core/dist/utils/dispatcher.js:20:33)","    at sendResponse (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/apollo-server-koa/node_modules/apollo-server-core/dist/requestPipeline.js:228:26)","    at processGraphQLRequest (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/apollo-server-koa/node_modules/apollo-server-core/dist/requestPipeline.js:177:12)","    at processTicksAndRejections (internal/process/task_queues.js:95:5)","    at async processHTTPRequest (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/apollo-server-koa/node_modules/apollo-server-core/dist/runHttpQuery.js:220:30)","    at async /home/jchardon/projects/strapy_cloudrun/strapi/node_modules/apollo-server-koa/dist/ApolloServer.js:82:59","    at async bodyParser (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/koa-bodyparser/index.js:95:5)","    at async returnBodyMiddleware (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/@strapi/strapi/lib/services/server/compose-endpoint.js:52:18)","    at async policiesMiddleware (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/@strapi/strapi/lib/services/server/policy.js:24:5)","    at async /home/jchardon/projects/strapy_cloudrun/strapi/node_modules/@strapi/strapi/lib/middlewares/logger.js:22:5","    at async /home/jchardon/projects/strapy_cloudrun/strapi/node_modules/@strapi/strapi/lib/middlewares/powered-by.js:16:5","    at async cors (/home/jchardon/projects/strapy_cloudrun/strapi/node_modules/@koa/cors/index.js:95:16)"]}}}]}

first request write in redis, second request crashing event when shouldReadFromCache and shouldWriteToCache is disable .

Hi

I’m having trouble with Apollo GraphQL caching. I’m using the same multiple collection type query for quering all entities and single entity. This is because I need to query single entity based on a slug for URL friendly endpoints. Since the “__typeName” coming back is “Project” on both of the cases but query bodies are different, Apollo warns that cache data might be lost since attributes content is different. I don’t want to query all the content of each project for listing the projects. Any idea on how to solve this ?

Thanks