# createWithCache Creates utility functions to store data in cache with stale-while-revalidate support. - Use `withCache.fetch` to simply fetch data from a third-party API. Fetches data from a URL and caches the result according to the strategy provided. When the response is not successful (e.g. status code >= 400), the caching is skipped automatically and the returned `data` is `null`. You can also prevent caching by using the `shouldCacheResponse` option and returning `false` from the function you pass in. For example, you might want to fetch data from a third-party GraphQL API but not cache the result if the GraphQL response body contains errors. - Use the more advanced `withCache.run` to execute any asynchronous operation. Utility function that executes asynchronous operations and caches the result according to the strategy provided. Use this to do any type of asynchronous operation where `withCache.fetch` is insufficient. For example, when making multiple calls to a third-party API where the result of all of them needs to be cached under the same cache key. Whatever data is returned from the `fn` will be cached according to the strategy provided. > Note: > To prevent caching the result you must throw an error. Otherwise, the result will be cached. > For example, if you call `fetch` but the response is not successful (e.g. status code >= 400), > you should throw an error. Otherwise, the response will be cached. ### Example code ```js // In your app's `server.ts` file: import * as remixBuild from '@remix-run/dev/server-build'; import {createWithCache, CacheLong} from '@shopify/hydrogen'; // Use another `createRequestHandler` if deploying off oxygen import {createRequestHandler} from '@shopify/remix-oxygen'; export default { async fetch(request, env, executionContext) { const cache = await caches.open('my-cms'); const withCache = createWithCache({ cache, waitUntil: executionContext.waitUntil.bind(executionContext), request, }); // 1. Create a custom utility to query a third-party API: const fetchMyCMS = async (query) => { const {data, response} = await withCache.fetch( 'https://my-cms.com/api', { method: 'POST', body: query, headers: {Authorization: 'Bearer 123'}, }, { // Optionally, specify a cache strategy. // Default is CacheShort(). cacheStrategy: CacheLong(), // Cache if there are no data errors or a specific data that make this result not suited for caching shouldCacheResponse: (result) => !(result?.errors || result?.isLoggedIn), // Optionally, add extra information to show // in the Subrequest Profiler utility. displayName: 'My CMS query', }, ); // Access the response properties: console.log(data, response.headers); return data; }; // 2. Or Create a more advanced utility to query multiple APIs under the same cache key: const fetchMultipleCMS = (options) => { // Prefix the cache key and make it unique based on arguments. return withCache.run( { // Define a cache key that is unique to this query cacheKey: ['my-cms-composite', options.id, options.handle], // Optionally, specify a cache strategy. // Default is CacheShort(). cacheStrategy: CacheLong(), // Cache if there are no data errors or a specific data that make this result not suited for caching shouldCacheResponse: (result) => !(result?.errors || result?.isLoggedIn), }, async (params) => { // Run multiple subrequests in parallel, or any other async operations. const [response1, response2] = await Promise.all([ fetch('https://my-cms-1.com/api', { method: 'POST', body: JSON.stringify({id: options.id}), }), fetch('https://my-cms-2.com/api', { method: 'POST', body: JSON.stringify({handle: options.handle}), }), ]); // Throw if any response is unsuccessful. // This is important to prevent the results from being cached. if (!response1.ok || !response2.ok) { throw new Error('Failed to fetch data'); } const [data1, data2] = await Promise.all([ response1.json(), response2.json(), ]); // Validate data and throw to avoid caching errors. if (data1.errors || data2.errors) { throw new Error('API errors'); } // Optionally, add extra information to show // in the Subrequest Profiler utility. params.addDebugData({displayName: 'My CMS query'}); // Compose the result as needed. return { ...data1, ...data2, extra1: response1.headers.get('X-Extra'), extra2: response2.headers.get('X-Extra'), }; }, ); }; const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, getLoadContext: () => ({ // Make sure to update env.d.ts to // include these properties in `AppLoadContext`. fetchMyCMS, fetchMultipleCMS, }), }); return handleRequest(request); }, }; ``` ```ts // In your app's `server.ts` file: import * as remixBuild from '@remix-run/dev/server-build'; import {createWithCache, CacheLong} from '@shopify/hydrogen'; // Use another `createRequestHandler` if deploying off oxygen import {createRequestHandler} from '@shopify/remix-oxygen'; export default { async fetch( request: Request, env: Record<string, string>, executionContext: ExecutionContext, ) { const cache = await caches.open('my-cms'); const withCache = createWithCache({ cache, waitUntil: executionContext.waitUntil.bind(executionContext), request, }); type ExpectedResponse = { content: unknown; isLoggedIn: boolean; errors?: string; }; type MergedResponse = { content: unknown; isLoggedIn: boolean; errors?: string; extra1: string | null; extra2: string | null; }; // 1. Create a custom utility to query a third-party API: const fetchMyCMS = async (query: string) => { const {data, response} = await withCache.fetch<ExpectedResponse>( 'https://my-cms.com/api', { method: 'POST', body: query, headers: {Authorization: 'Bearer 123'}, }, { // Optionally, specify a cache strategy. // Default is CacheShort(). cacheStrategy: CacheLong(), // Cache if there are no data errors or a specific data that make this result not suited for caching shouldCacheResponse: (result) => !(result?.errors || result?.isLoggedIn), // Optionally, add extra information to show // in the Subrequest Profiler utility. displayName: 'My CMS query', }, ); // Access the response properties: console.log(data, response.headers); return data; }; // 2. Or Create a more advanced utility to query multiple APIs under the same cache key: const fetchMultipleCMS = (options: {id: string; handle: string}) => { // Prefix the cache key and make it unique based on arguments. return withCache.run( { // Define a cache key that is unique to this query cacheKey: ['my-cms-composite', options.id, options.handle], // Optionally, specify a cache strategy. // Default is CacheShort(). cacheStrategy: CacheLong(), // Cache if there are no data errors or a specific data that make this result not suited for caching shouldCacheResult: (result: MergedResponse) => !(result?.errors || result?.isLoggedIn), }, async (params) => { // Run multiple subrequests in parallel, or any other async operations. const [response1, response2] = await Promise.all([ fetch('https://my-cms-1.com/api', { method: 'POST', body: JSON.stringify({id: options.id}), }), fetch('https://my-cms-2.com/api', { method: 'POST', body: JSON.stringify({handle: options.handle}), }), ]); // Throw if any response is unsuccessful. // This is important to prevent the results from being cached. if (!response1.ok || !response2.ok) { throw new Error('Failed to fetch data'); } const [data1, data2] = (await Promise.all([ response1.json(), response2.json(), ])) as [ExpectedResponse, ExpectedResponse]; // Validate data and throw to avoid caching errors. if (data1.errors || data2.errors) { throw new Error('API errors'); } // Optionally, add extra information to show // in the Subrequest Profiler utility. params.addDebugData({displayName: 'My CMS query'}); // Compose the result as needed. return { ...data1, ...data2, extra1: response1.headers.get('X-Extra'), extra2: response2.headers.get('X-Extra'), } as MergedResponse; }, ); }; const handleRequest = createRequestHandler({ build: remixBuild, mode: process.env.NODE_ENV, getLoadContext: () => ({ // Make sure to update env.d.ts to // include these properties in `AppLoadContext`. fetchMyCMS, fetchMultipleCMS, }), }); return handleRequest(request); }, }; ``` ## Arguments ### CreateWithCacheGeneratedType #### Returns: WithCache #### Params: - cacheOptions: CreateWithCacheOptions export function createWithCache( cacheOptions: CreateWithCacheOptions, ): WithCache { const {cache, waitUntil, request} = cacheOptions; return { run: <T>( {cacheKey, cacheStrategy, shouldCacheResult}: WithCacheRunOptions<T>, fn: ({addDebugData}: CacheActionFunctionParam) => T | Promise<T>, ): Promise<T> => { return runWithCache(cacheKey, fn, { shouldCacheResult, strategy: cacheStrategy, cacheInstance: cache, waitUntil, debugInfo: { ...getDebugHeaders(request), stackInfo: getCallerStackLine?.(), }, }); }, fetch: <T>( url: string, requestInit: RequestInit, options: WithCacheFetchOptions<T>, ): Promise<{data: T | null; response: Response}> => { return fetchWithServerCache<T | null>(url, requestInit ?? {}, { waitUntil, cacheKey: [url, requestInit], cacheInstance: cache, debugInfo: { url, ...getDebugHeaders(request), stackInfo: getCallerStackLine?.(), displayName: options?.displayName, }, cache: options.cacheStrategy, ...options, }).then(([data, response]) => ({data, response})); }, }; } ### CreateWithCacheOptions ### cache An instance that implements the [Cache API](https://developer.mozilla.org/en-US/docs/Web/API/Cache) ### request The `request` object is used by the Subrequest profiler, and to access certain headers for debugging ### waitUntil The `waitUntil` function is used to keep the current request/response lifecycle alive even after a response has been sent. It should be provided by your platform. ### CrossRuntimeRequest ### headers ### method ### url ### WithCache ### fetch ### run ### WithCacheFetchOptions ### cacheKey The cache key for this fetch ### cacheStrategy Use the `CachingStrategy` to define a custom caching mechanism for your data. Or use one of the pre-defined caching strategies: [`CacheNone`](/docs/api/hydrogen/utilities/cachenone), [`CacheShort`](/docs/api/hydrogen/utilities/cacheshort), [`CacheLong`](/docs/api/hydrogen/utilities/cachelong). ### displayName ### shouldCacheResponse Useful to avoid e.g. caching a successful response that contains an error in the body ### CacheKey The cache key is used to uniquely identify a value in the cache. string | readonly unknown[] ### CachingStrategy Use the `CachingStrategy` to define a custom caching mechanism for your data. Or use one of the pre-defined caching strategies: CacheNone, CacheShort, CacheLong. ### maxAge The maximum amount of time in seconds that a resource will be considered fresh. See `max-age` in the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#:~:text=Response%20Directives-,max%2Dage,-The%20max%2Dage). ### mode The caching mode, generally `public`, `private`, or `no-store`. ### sMaxAge Similar to `maxAge` but specific to shared caches. See `s-maxage` in the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#s-maxage). ### staleIfError Indicate that the cache should serve the stale response if an error occurs while revalidating the cache. See `stale-if-error` in the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-if-error). ### staleWhileRevalidate Indicate that the cache should serve the stale response in the background while revalidating the cache. See `stale-while-revalidate` in the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#stale-while-revalidate). ### WithCacheRunOptions ### cacheKey The cache key for this run ### cacheStrategy Use the `CachingStrategy` to define a custom caching mechanism for your data. Or use one of the pre-defined caching strategies: [`CacheNone`](/docs/api/hydrogen/utilities/cachenone), [`CacheShort`](/docs/api/hydrogen/utilities/cacheshort), [`CacheLong`](/docs/api/hydrogen/utilities/cachelong). ### shouldCacheResult Useful to avoid accidentally caching bad results ### CacheActionFunctionParam ### addDebugData ### AddDebugDataParam ### displayName ### response