Server-side caching is crucial for reducing database load, lowering latency, and can be implemented at a few different levels.
Resolver-Level Caching with DataLoader
Operation-Level (Response) Caching
Persisted Queries
Persistent Caching and CDNs
Middleware-Level Caching
Resolver-Level Caching with DataLoader: This is your first line of defense. It prevents the infamous N+1 problem (e.g., fetching a list of 100 posts, then making 100 separate author queries) by batching and caching requests in a single request cycle. The DataLoader utility is key here, but it's crucial to create a new DataLoader instance for each incoming request to prevent data leakage between different users. While primarily for batching, DataLoader includes a basic per-request cache to avoid duplicate lookups for the same key within a single GraphQL operation
Operation-Level (Response) Caching: A more advanced technique that caches the results of entire GraphQL queries based on their query string and variables. For example, the cache key might look like hash(operationString, stringify(variables)). To be safe for user-specific data, the cache key must also incorporate a requestor-specific identifier like a user ID to prevent serving one user's data to another. Because invalidating this cache can be complex, many teams start with a simple Time To Live (TTL) strategy before building more sophisticated, mutation-triggered invalidation logic
Persisted Queries: This technique dramatically shrinks request payloads and enables GET-based caching. The client sends a short, unique hash (e.g., SHA-256) instead of the full query string to the server, which looks up the original query from its cache. This works with both POST and GET methods. Paired with a CDN, it allows for extremely fast responses, especially for large, complex queries
Persistent Caching and CDNs: For requests that are truly public (e.g., product listings), you can leverage a CDN. Because many CDNs don't cache POST requests by default, you must use GET requests. Combined with "Persisted Queries" (to reduce URL length), and standard HTTP Cache-Control headers, you can enable powerful CDN-level caching. Apollo Server's response caching plugin also provides cache tags that work like CDN surrogate keys for granular invalidation
Middleware-Level Caching: You can also cache at the HTTP middleware level by integrating a caching library (like graphql-redis-cache) to store query results in a distributed cache like Redis before they reach the GraphQL execution engine