Last active
April 11, 2023 08:52
-
-
Save chregu/8e3edf2a07330171a1ba217fdb07e96d to your computer and use it in GitHub Desktop.
Middleware Caching Vendure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { caching } from 'cache-manager' | |
import redisStore from 'cache-manager-ioredis' | |
import crypto from 'crypto' | |
import { NextFunction, Request, Response, Send } from 'express' | |
import { DocumentNode, parse } from 'graphql' | |
import { DefinitionNode } from 'graphql/language/ast' | |
const redisCacheConfig = { | |
store: redisStore, | |
host: process.env.REDIS_HOST || 'localhost', | |
port: process.env.REDIS_PORT || 6457, | |
password: process.env.REDIS_PASSWORD, | |
ttl: 60, | |
db: 2, | |
} | |
const cache = caching(redisCacheConfig) | |
type QueryConfig = { | |
ttl: number | |
} | |
const defaultConfig: QueryConfig = { ttl: 300 } | |
// only these queries will be cached | |
const config: Record<string, QueryConfig> = { | |
products: defaultConfig, | |
pagesBySection: defaultConfig, | |
} | |
// parse graphql query, from https://stackoverflow.com/a/59055312 | |
const operationDefinitions = (ast: DocumentNode): DefinitionNode[] => | |
ast.definitions.filter((d: any) => d.kind === 'OperationDefinition') | |
const fieldValueNamesFromOperation = (operationDefinitions: DefinitionNode[]): QueryNames[] => { | |
return operationDefinitions | |
.map((operationDefinition: any) => { | |
if ('selectionSet' in operationDefinition) { | |
return operationDefinition.selectionSet.selections.map((s: any) => s.name.value) | |
} | |
return [] | |
}) | |
.flat() | |
} | |
export const middlewareCaching = async (req: Request, res: Response, next: NextFunction) => { | |
// only cache post requests, if you do graphql via GET, change this | |
if (req.method !== 'POST') { | |
next() | |
return | |
} | |
// not sure, if this is the correct way to do it,so that the body is available later again | |
const body = req.body | |
req.body = body | |
// parse graphql query, from https://stackoverflow.com/a/59055312 | |
const parsedQuery = parse(body.query) | |
const operations = operationDefinitions(parsedQuery) | |
if (operations.length === 0) { | |
next() | |
return | |
} | |
// don't cache, if there's a mutation in it | |
if (operations.find((o: any) => o.operation === 'mutation')) { | |
next() | |
return | |
} | |
const queryNames = fieldValueNamesFromOperation(operations) | |
//dont cache schema | |
if (queryNames.includes('__schema')) { | |
next() | |
return | |
} | |
// no queryNames found, don't cache | |
if (queryNames.length === 0) { | |
next() | |
return | |
} | |
for (const queryName of queryNames) { | |
// don't cache whole query, if one query is not in config | |
if (!config[queryName]) { | |
next() | |
return | |
} | |
} | |
const shasum = crypto.createHash('sha1') | |
shasum.update(JSON.stringify(body) + JSON.stringify(req.query)) | |
const key = `graphql:${queryNames}:${shasum.digest('hex')}` | |
try { | |
const obj = await cache.get(key) | |
if (obj) { | |
res.send(obj) | |
return | |
} | |
type ResponseWithSend = Response & { sendResponse: Send } | |
const responseWithSend = res as ResponseWithSend | |
responseWithSend.sendResponse = res.send | |
const ttl = Math.min(...queryNames.map(queryName => config[queryName]?.ttl || defaultConfig.ttl)) | |
res.send = body => { | |
console.log(`set cache for query ${queryNames} with key ${key} and ttl: ${ttl}`) | |
cache.set(key, body, { ttl: ttl }) | |
return responseWithSend.sendResponse(body) | |
} | |
} catch (e) { | |
console.error(e) | |
} | |
next() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
And put this into your vendure-config.ts
This will cache some of your GraphQL Queries (the one defined in the config variable). Make sure you don't cache user specific stuff and also make sure to clear the cache, when something changes (like stocks, so basically you should clear the cache, whenever someone buys something and you do stock checking via the query)