From 9b365fab69a3fa95a8bedadd20d3525b5c2e8c02 Mon Sep 17 00:00:00 2001 From: David Thyresson Date: Wed, 8 May 2024 05:32:04 -0400 Subject: [PATCH] docs: Adds GraphQL Caching docs for Client and Response caching (#10054) When v7 documentation was deployed, somehow the `useCache` hook documentation for interacting with the Apollo Client cache was omitted (or I lost it). This PR Adds a Caching sidebar to the GraphQL documentation to cover both client and server (aka response) caching. ![image](https://github.com/redwoodjs/redwood/assets/1051633/90036d5a-934a-4be8-93b6-d79a876e9348) --------- Co-authored-by: Dominic Saadi Co-authored-by: Josh GM Walker <56300765+Josh-Walker-GM@users.noreply.github.com> --- .changesets/10054.md | 3 + docs/docs/graphql/caching.md | 239 +++++++++++++++++++++++++++++++++ docs/docs/graphql/fragments.md | 6 +- docs/sidebars.js | 1 + 4 files changed, 247 insertions(+), 2 deletions(-) create mode 100644 .changesets/10054.md create mode 100644 docs/docs/graphql/caching.md diff --git a/.changesets/10054.md b/.changesets/10054.md new file mode 100644 index 000000000000..4ed0a1a74677 --- /dev/null +++ b/.changesets/10054.md @@ -0,0 +1,3 @@ +- docs: Adds GraphQL Caching docs for Client and Response caching (#10054) by @dthyresson + +This PR introduces new GraphQL documentation to cover both client and server (aka response) caching. diff --git a/docs/docs/graphql/caching.md b/docs/docs/graphql/caching.md new file mode 100644 index 000000000000..87c899470d28 --- /dev/null +++ b/docs/docs/graphql/caching.md @@ -0,0 +1,239 @@ +# GraphQL Caching + +## Client Caching + +Apollo Client stores the results of your GraphQL queries in a local, normalized, in-memory cache. + +Redwood provides a custom hook called `useCache` that makes it more convenience to access and use the cache object. + +Please refer to Apollo's documentation for complete information about [Caching in Apollo Client](https://www.apollographql.com/docs/react/caching/overview). + +### useCache Hook + +`useCache` is a custom hook that returns the cache object and some useful methods to interact with the cache. + +```ts title="Example of useCache() hook" +import { useCache } from '@redwoodjs/web/apollo' + +const CacheExample = () => { + const { cache, evict, extract, identify, modify, resetStore, clearStore } = useCache() + + // ... +} +``` + +### Helper Methods + +#### cache + +The cache object itself. + +With `cache` you can access methods on the cache not exposed as helpers here, such as `readQuery` or `gc` for garbage collections. See Apollo's [caching interaction](https://www.apollographql.com/docs/react/caching/cache-interaction) documentation. + +:::tip +To help understand the structure of your cached data, you can install the [Apollo Client Devtools](https://www.apollographql.com/docs/react/development-testing/developer-tooling/#apollo-client-devtools). + +This browser extension includes an inspector that enables you to view all of the normalized objects contained in your cache. +::: + +Alternatively, see [extract](#extract) to get a normalized cache object you can inspect. + +#### evict + +Either removes a normalized object from the cache or removes a specific field from a normalized object in the cache. + +```ts title="Example of evict" +import { useCache } from '@redwoodjs/web/apollo' + +const CacheExample = () => { + const { evict } = useCache() + + // You can remove any normalized object from the cache using the evict method: + evict({ id: 'Post:123' }) + + // You can also remove a single field from a cached object by providing the name of the field to remove + evict({ id: 'Post:123', fieldName: 'title' }); +} +``` + +#### extract + +Returns a serialized representation of the cache's current contents + +```ts title="Example of extract" +import { useCache } from '@redwoodjs/web/apollo' + +const CacheExample = () => { + const { extract } = useCache() + + console.log(extract()) +} +``` + +#### identify + +If a type in your cache uses a custom cache ID (or even if it doesn't), you can use the `cache.identify` method to obtain the cache ID for an object of that type. + +This method takes an object and computes its ID based on both its `__typename` and its identifier field(s). + +This means you don't have to keep track of which fields make up each type's cache ID. + +```ts title="Example of identify" +import { useCache } from '@redwoodjs/web/apollo' + +const CacheExample = () => { + const { identify } = useCache() + + const id = identify({ __typename: 'User', id: 1 }) + + console.log(id) +} +``` + + +#### modify + +Modifies one or more field values of a cached object. + +You must provide a modifier function for each field to modify. A modifier function takes a cached field's current value and returns the value that should replace it. + +Returns `true` if the cache was modified successfully and `false` otherwise. + +```ts title="Example of modify" +import { useCache } from '@redwoodjs/web/apollo' + +const CacheExample = () => { + const { modify } = useCache() + + const id = identify({ __typename: 'User', id: 1 }) + + modify(id, { + name: 'David', + }) +} +``` + +#### resetStore + +Reset the cache entirely, such as when a user logs out. + +See Apollo's [Resetting the Cache](https://www.apollographql.com/docs/react/caching/advanced-topics#resetting-the-cache) for more details. + +```ts title="Example of resetStore" +import { useCache } from '@redwoodjs/web/apollo' + +const Logout = () => { + const { resetStore } = useCache() + + return ( + + ) +} +``` + +#### clearStore + +To reset the cache without refetching active queries, use `clearStore`. + +See Apollo's documentation on [Resetting the Cache](https://www.apollographql.com/docs/react/caching/advanced-topics#resetting-the-cache) for more details. + +```ts title="Example of clearStore" +import { useCache } from '@redwoodjs/web/apollo' + +const Logout = () => { + const { clearStore } = useCache() + + return ( + + ) +} +``` + +### Persisting Cache + +Apollo Client allows you [persist and rehydrate](https://www.apollographql.com/docs/react/caching/advanced-topics/#persisting-the-cache) the `InMemoryCache` from a storage provider like `AsyncStorage` or `localStorage`. To do so, use the `apollo3-cache-persist` package. This package works with a variety of storage providers. + +To get started, pass your `cache` from the `useCache` hook and a storage provider to `persistCache`. By default, the contents of your cache are immediately restored asynchronously, and they're persisted on every write to the cache with a short, configurable debounce interval. + +:::note + +The persistCache method is async and returns a Promise. + +::: + +```jsx title="Example of persisting cache" +import { persistCache, LocalStorageWrapper } from 'apollo3-cache-persist' + +import { useCache } from '@redwoodjs/web/apollo' + +const PersistCacheExample = async () => { + const { cache } = useCache() + + await persistCache({ + cache, + storage: new LocalStorageWrapper(window.localStorage), + }) + + // ... +} +``` + +## Response Caching + +[Response caching](https://the-guild.dev/graphql/yoga-server/docs/features/response-caching) is a technique for reducing server load by caching GraphQL query operation results. For incoming GraphQL Query operations with the same variable values, the same response is returned from a cache instead of executed again. + +Redwood's GraphQL Server offers response caching via the `useResponseCache` [GraphQL Yoga plugin](https://github.com/dotansimha/graphql-yoga/tree/main/packages/plugins/response-cache). + +### Setup + +To setup response caching, first install `@graphql-yoga/plugin-response-cache`: + +```bash +yarn workspace api add @graphql-yoga/plugin-response-cache +``` + +And then modify your `api/src/functions/graphql.ts` function to add (and configure) the `useResponseCache` plugin to the handler's `extraPlugins`: + +```ts title="Example of GraphQL Response Caching" +import { useResponseCache } from '@graphql-yoga/plugin-response-cache' + +import { createGraphQLHandler } from '@redwoodjs/graphql-server' + +import directives from 'src/directives/**/*.{js,ts}' +import sdls from 'src/graphql/**/*.sdl.{js,ts}' +import services from 'src/services/**/*.{js,ts}' + +import { db } from 'src/lib/db' +import { logger } from 'src/lib/logger' + +export const handler = createGraphQLHandler({ + loggerConfig: { logger, options: {} }, + directives, + sdls, + services, + extraPlugins: [ + useResponseCache({ + session: () => null, + ttlPerSchemaCoordinate: { + 'Query.recentPosts': 10 * 1_000, // cache the `recentPosts` query for 10 seconds + }, + }), + ], + onException: () => { + // Disconnect from your database with an unhandled exception. + db.$disconnect() + }, +}) +``` + +### In-Memory vs External Caching + + +By default, the response cache stores all the cached query results in memory. That means if you have deployed to a serverless hosting platform, the cache only lives per-request. + +In this case you would want to use an [External Cache](https://the-guild.dev/graphql/yoga-server/docs/features/response-caching#external-cache) like Redis. + diff --git a/docs/docs/graphql/fragments.md b/docs/docs/graphql/fragments.md index 1ab2a879f085..bd1fea746b93 100644 --- a/docs/docs/graphql/fragments.md +++ b/docs/docs/graphql/fragments.md @@ -176,8 +176,6 @@ fragment BookInfo on Book { the `typename` is `Book`. -## useCache!!! - ## getCacheKey A helper function to create the cache key for the data associated with the fragment in Apollo cache. @@ -200,6 +198,10 @@ fragment BookInfo on Book { the `getCacheKey` is a function where `getCacheKey(42)` would return `Book:42`. +:::tip +We describe how [cache keys and identifiers](./caching.md#identify) are used in more depth in the our [Apollo client cache](./caching.md#client-caching) documentation. +::: + ### useRegisteredFragment ```ts diff --git a/docs/sidebars.js b/docs/sidebars.js index 7d4d191a52e8..d378b67ebf77 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -153,6 +153,7 @@ module.exports = { }, items: [ { type: 'doc', label: 'About', id: 'graphql' }, + { type: 'doc', label: 'Caching', id: 'graphql/caching' }, { type: 'doc', label: 'Fragments', id: 'graphql/fragments' }, { type: 'doc',