Last active
November 21, 2024 17:03
-
-
Save luksak/def035c8ed37892d967a05186efa180a to your computer and use it in GitHub Desktop.
Integration of nuxt-graphql-middleware with nuxt-multi-cache with full cache tag propagation
This file contains hidden or 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
export default defineNuxtPlugin((NuxtApp) => { | |
const state = useGraphqlState() | |
const event = useRequestEvent() | |
if (!state) { | |
return | |
} | |
state.fetchOptions = { | |
onResponse ({ request, response, options }) { | |
if (response._data.__cacheTags) { | |
const cacheTags = response._data.__cacheTags | |
useCDNHeaders((helper) => { | |
helper | |
.public() | |
.setNumeric('maxAge', 3600) | |
.set('staleIfError', 86400) | |
.set('staleWhileRevalidate', 86400) | |
.set('sharedMaxAge', 2592000) | |
.addTags(cacheTags) | |
}, event) | |
} | |
} | |
} | |
}) |
This file contains hidden or 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 { H3Event } from 'h3' | |
import { defineGraphqlServerOptions } from 'nuxt-graphql-middleware/dist/runtime/serverOptions' | |
import { GraphqlMiddlewareOperation } from 'nuxt-graphql-middleware/dist/runtime/settings' | |
import type { GraphqlMiddlewareRuntimeConfig } from 'nuxt-graphql-middleware/dist/runtime/types' | |
// @todo: Mocking the missing functions from nuxt-graphql-middleware | |
import { getFetchOptions, onServerError, onServerResponse, getEndpoint } from '../utils/middlewareFunctions' | |
import serverOptions from '#graphql-middleware-server-options-build' | |
import { useDataCache, useCDNHeaders } from '#nuxt-multi-cache/composables' | |
import useAddCDNHeaders from '~/composables/useAddCDNHeaders' | |
export default defineGraphqlServerOptions({ | |
/** | |
* Alter response from GraphQL server. | |
*/ | |
onServerResponse (event, graphqlResponse) { | |
const surrogateKey = graphqlResponse.headers.get('Surrogate-Key') | |
const cacheTags = surrogateKey.split(' ') | |
return { ...graphqlResponse._data, __cacheTags: cacheTags } | |
}, | |
/** | |
* Cache requests in nuxt-multi-cache | |
*/ | |
async doGraphqlRequest ({ | |
event, | |
operationName, | |
operationDocument, | |
variables | |
}) { | |
// The operation (either "query" or "mutation"). | |
const operation = event.context?.params | |
?.operation as GraphqlMiddlewareOperation | |
// Determine the endpoint of the GraphQL server. | |
// Get the fetch options for this request. | |
const fetchOptions = await getFetchOptions( | |
serverOptions, | |
event, | |
operation, | |
operationName | |
) | |
// Get the runtime config. | |
const runtimeConfig = useRuntimeConfig() | |
.graphqlMiddleware as GraphqlMiddlewareRuntimeConfig | |
// Determine the endpoint of the GraphQL server. | |
const endpoint = await getEndpoint( | |
runtimeConfig, | |
serverOptions, | |
event, | |
operation, | |
operationName | |
) | |
const cacheKey = `${operationName}|${JSON.stringify(variables)}` | |
const { value: cachedValue, addToCache } = await useDataCache(cacheKey, event) | |
// If the data is already in cache, return it | |
if (cachedValue) { | |
useAddCDNHeaders(event, cachedValue.__cacheTags) | |
return cachedValue | |
} | |
// The data isn't available in cache. We have to fetch it. | |
const response = await $fetch | |
.raw(endpoint, { | |
// @todo: Remove any once https://github.com/unjs/nitro/pull/883 is released. | |
method: 'POST' as any, | |
body: { | |
query: operationDocument, | |
variables, | |
operationName | |
}, | |
...fetchOptions | |
}) | |
.then((response) => { | |
return onServerResponse( | |
serverOptions, | |
event, | |
response, | |
operation, | |
operationName | |
) | |
}) | |
.catch((error) => { | |
return onServerError( | |
serverOptions, | |
event, | |
error, | |
operation, | |
operationName | |
) | |
}) | |
useAddCDNHeaders(event, response.__cacheTags) | |
// Save data to the cache and return it. | |
addToCache(response) | |
return response | |
} | |
}) |
This file contains hidden or 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
// This file only exists since nuxt-graphql-middleware doesn't export all functions needed in ../app/graphqlMiddleware.serverOptions.ts | |
import type { H3Event } from 'h3' | |
import type { GraphqlMiddlewareServerOptions, GraphqlMiddlewareRuntimeConfig } from 'nuxt-graphql-middleware/dist/runtime/types' | |
import { GraphqlMiddlewareOperation } from 'nuxt-graphql-middleware/dist/runtime/settings' | |
import type { FetchOptions, FetchResponse, FetchError } from 'ofetch' | |
export function getFetchOptions ( | |
serverOptions: GraphqlMiddlewareServerOptions, | |
event: H3Event, | |
operation: GraphqlMiddlewareOperation, | |
operationName: string | |
): FetchOptions | Promise<FetchOptions> { | |
if (serverOptions.serverFetchOptions) { | |
return ( | |
serverOptions.serverFetchOptions(event, operation, operationName) || {} | |
) | |
} | |
return {} | |
} | |
/** | |
* Handle GraphQL server response. | |
*/ | |
export function onServerResponse ( | |
serverOptions: GraphqlMiddlewareServerOptions, | |
event: H3Event, | |
response: FetchResponse<any>, | |
operation?: string, | |
operationName?: string | |
) { | |
if (serverOptions.onServerResponse) { | |
return serverOptions.onServerResponse( | |
event, | |
response, | |
operation, | |
operationName | |
) | |
} | |
return response._data | |
} | |
/** | |
* Handle GraphQL server errors. | |
*/ | |
export function onServerError ( | |
serverOptions: GraphqlMiddlewareServerOptions, | |
event: H3Event, | |
error: FetchError, | |
operation?: string, | |
operationName?: string | |
) { | |
if (serverOptions.onServerError) { | |
return serverOptions.onServerError(event, error, operation, operationName) | |
} | |
const message = error && 'message' in error ? error.message : '' | |
throw createError({ | |
statusCode: 500, | |
statusMessage: "Couldn't execute GraphQL query: " + message, | |
data: error && 'message' in error ? error.message : error | |
}) | |
} | |
/** | |
* Get the URL of the GraphQL endpoint. | |
*/ | |
export function getEndpoint ( | |
config: GraphqlMiddlewareRuntimeConfig, | |
serverOptions: GraphqlMiddlewareServerOptions, | |
event: H3Event, | |
operation: GraphqlMiddlewareOperation, | |
operationName: string | |
): string | Promise<string> { | |
// Check if a custom graphqlEndpoint method exists. | |
if (serverOptions.graphqlEndpoint) { | |
const result = serverOptions.graphqlEndpoint( | |
event, | |
operation, | |
operationName | |
) | |
// Only return if the method returned somethind. This way we fall back to | |
// config at build time. | |
if (result) { | |
return Promise.resolve(result) | |
} | |
} | |
if (config.graphqlEndpoint) { | |
return config.graphqlEndpoint | |
} | |
throw new Error('Failed to determine endpoint for GraphQL server.') | |
} |
This file contains hidden or 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 { H3Event } from 'h3' | |
import { useCDNHeaders } from '#nuxt-multi-cache/composables' | |
export default function (event: H3Event, cacheTags: string[]) { | |
useCDNHeaders((helper) => { | |
helper | |
.public() | |
.setNumeric('maxAge', 3600) | |
.set('staleIfError', 86400) | |
.set('staleWhileRevalidate', 86400) | |
.set('sharedMaxAge', 2592000) | |
.addTags(cacheTags) | |
}, event) | |
} |
@plcdnl I just added the file. It actually just mocks the functions that nuxt-graphql-middleware
didn't export. So nothing important there. I hope it helps!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for the example @luksak ! Could you share also the middleware functions? I would like to use it for Craft CMS too and happy to contribute on your code.
import { getFetchOptions, onServerError, onServerResponse, getEndpoint } from '../utils/middlewareFunctions'