Develop highly performing graphql API in Strapi using APQ and Caching

By Krishna Deo Rai


To make our Strapi Api performant, we wanted to implement automatic persisted queries and caching in Strapi.

Because CDNs and caching proxies only cache GET requests (not POST requests, which Apollo Client sends for all operations by default). So it was recommended to enable automatic persisted queries and the useGETForHashedQueries option in Apollo Client.

1. Automated Persisted Queries: 
Clients send queries to Apollo Server as HTTP requests that include the GraphQL string of the query to execute. Depending on your graph’s schema, the size of a valid query string might be large. As query strings become larger, increased latency and network usage can noticeably degrade client performance.

To improve network performance for large query strings, Apollo Server supports Automatic Persisted Queries (APQ). A persisted query is a query string that is cached on the server side, along with its unique identifier (always its SHA-256 hash). Clients can send this identifier instead of the corresponding query string, thus reducing request sizes dramatically (response sizes are unaffected).

Automatic Persisted Queries(APQ) & Caching in Strapi
Automatic Persisted Queries(APQ) & Caching in Strapi

2. Implementing APQ and Caching in Strapi v4

The Strapi plugin is built using Apollo Server. If you configure the GraphQL plugin settings you can enable caching and APQ for Apollo Server.

Graphql is initialised in config/plugins.js and related code changes of APQ and caching are added under apolloServer key in config/plugins.js.

3. APQ changes:

persistedQueries: {

ttl: APQ_MAX_AGE,

cache: cache,

},

Sample Query:

curl — get http://localhost:1337/graphql \

— data-urlencode ‘query={testContents {data { attributes { test1 test2}}}}’ \

— data-urlencode ‘extensions={“persistedQuery”:{“version”:1,”sha256Hash”:”ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38″}}’

If sha256Hash is not proper then it will return an error message provided sha does not match the query because there is a check at line no 56 in node_modules/apollo-server-core/dist/requestPipeline.js.

const computedQueryHash = computeQueryHash(query);

if (queryHash !== computedQueryHash) {

return await sendErrorResponse(new graphql_1.GraphQLError(‘provided sha does not match query’));

}

4. Caching changes:

cacheControl: { defaultMaxAge: CACHE_MAX_AGE },

cache: cache,

plugins: [apolloServerPluginResponseCache.default({

shouldReadFromCache,

shouldWriteToCache,

extraCacheKeyData,

sessionId,

}),

injectCacheControl()

]

Apollo caching gives the flexibility to control caching per field basis.

We made changes for overall Graphql caching to work.

function injectCacheControl() {

return {

requestDidStart(requestContext) {

requestContext.overallCachePolicy.policyIfCacheable({ scope: ‘PUBLIC’, maxAge: CACHE_MAX_AGE });

requestContext.overallCachePolicy.scope = ‘PUBLIC’;

requestContext.overallCachePolicy.maxAge = CACHE_MAX_AGE

}

}
}

Added policyIfCacheable by referring to line 101 in node_modules/apollo-server-plugin-response-cache/dist/ApolloServerPluginResponseCache.js

5. Complete Code

https://gist.github.com/krishnadeorai/e3e390c55737d2baff3afdba240d665e

→ New apollo server is created in node_modules/@strapi/plugin-graphql/server/bootstrap.js

→ Logic of loading config of persisted queries and initialising of persisted queries are present in node_modules/apollo-server-core/dist/ApolloServer.js, node_modules/apollo-server-types/src/index.ts


Reference:

https://forum.strapi.io/t/caching-graphql-query-results-with-strapi/1407https://forum.strapi.io/t/caching-graphql-query-results-with-strapi/1407


Meet the team!

Author

Krishna Deo Rai

Editor

Seema Jain


We at CaratLane are solving some of the most intriguing challenges to make our mark in the relatively uncharted omnichannel jewellery industry. If you are interested in tackling such obstacles, feel free to drop your updated resume/CV to careers@caratlane.com!