Skip to main content

Caching with Apollo Server

A Guide to Using ApolloServerPluginCacheControl and Redis

Apollo Server provides multiple ways to implement caching, helping reduce server load and improve response times for frequently requested data. This document outlines how caching works in Apollo Server and how to use the ApolloServerPluginCacheControl and responseCachePlugin to store cache in Redis.

1. How Caching Works in Apollo Server

Apollo Server supports two types of caching:

  • In-memory cache: Data is temporarily stored on the server to avoid recomputation on subsequent identical queries.
  • External cache (e.g., Redis): Data is stored in external caching layers, such as Redis, allowing caching across multiple servers.

To enable caching, Apollo Server relies on two key plugins:

  • ApolloServerPluginCacheControl: Controls caching at the field level using cache directives and provides metadata like maxAge (TTL) for each field or query.
  • responseCachePlugin: Caches full query responses based on the operation (i.e., the entire query and its result).

2. Caching with ApolloServerPluginCacheControl

ApolloServerPluginCacheControl allows you to define cache control settings directly in your GraphQL schema using the @cacheControl directive or globally through server settings.

Key Features:

  • maxAge: Sets the time (in seconds) for which a response or field can be cached.
  • scope: Defines if the cache is shared (public) or user-specific (private).

Example:

type Query {
favouriteBooks: [Book] @cacheControl(maxAge: 60, scope: Private)
books: [Book] @cacheControl(maxAge: 60)
bookByTitle(title: String!): Book @cacheControl(maxAge: 30)
}

In this example, the books query response is cached for 60 seconds, while bookByTitle is cached for 30 seconds, both of them will be cached once whereas the favouriteBooks due to its private scope will be cached differently against each user request.

Default Cache Control:

If no @cacheControl directive is used, you can specify a default maxAge globally. PS: We do not use Global Max Age

const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
ApolloServerPluginCacheControl({
defaultMaxAge: 10, // Default cache duration of 10 seconds
}),
],
});

3. Using the responseCachePlugin with Redis

The responseCachePlugin is designed to cache entire query responses (not just field-level caching). By integrating Redis as the cache storage, responses can be cached externally and shared across multiple instances of Apollo Server.

Steps to Enable Response Caching in Redis:

  1. Install Redis and Required Packages: Install Redis and its Node.js client (ioredis) to interact with the Redis server.

    npm install ioredis @apollo/server-plugin-response-cache
  2. Setup Redis Client: Initialize a Redis client using ioredis and configure the cache storage.

    const Redis = require('ioredis');
    const redis = new Redis();
  3. Configure the responseCachePlugin: Use the responseCachePlugin to store query results in Redis.

    const { ApolloServer } = require('@apollo/server');
    const { responseCachePlugin } = require('@apollo/server-plugin-response-cache');

    const server = new ApolloServer({
    typeDefs,
    resolvers,
    cache: redisCache,
    plugins: [
    responseCachePlugin({
    shouldReadFromCache: async (requestContext) => {
    const { maxAge } = requestContext.overallCachePolicy || {};
    return maxAge > 0; // Cache only if maxAge is set
    },
    shouldWriteToCache: async (requestContext) => {
    const { maxAge } = requestContext.overallCachePolicy || {};
    return maxAge > 0; // Write to cache only if maxAge is set
    },
    }),
    ],
    });
  4. Server Configuration: Add both ApolloServerPluginCacheControl (for defining cache policies) and responseCachePlugin (for full-response caching) to your Apollo Server configuration.

    const { ApolloServerPluginCacheControl } = require('@apollo/server-plugin-cache-control');

    const server = new ApolloServer({
    typeDefs,
    resolvers,
    plugins: [
    ApolloServerPluginCacheControl({
    }),
    responseCachePlugin({
    ...pluginOptions
    }),
    ],
    });

4. How the Caching Workflow Operates

  • Field-level Cache Control: The ApolloServerPluginCacheControl checks for @cacheControl directives or uses the default maxAge to define TTLs for each query/field.
  • Full Response Caching: The responseCachePlugin caches the full result of a query if the request is cacheable (i.e., if maxAge is set).
  • Redis as Cache Store: Query responses are stored in Redis, and when a cached query is requested again, the response is served from Redis, avoiding the need for computation.

5. Cache Invalidation

As we know, Redis stores cached data with a TTL (Max Age) value, and it automatically expires once that time limit is reached. However, in scenarios where the actual data changes before the cache expires, we need a way to invalidate the cache on demand to ensure the application always returns up-to-date information.

To address this, we’ve developed a custom plugin called invalidateCachePlugin. This plugin introduces a custom directive that can be applied to mutations. With this directive, you can specify a list of queries to invalidate. Whenever the mutation runs successfully, the cache for all the listed queries is automatically cleared, ensuring the application serves fresh data.

Example:


type Mutation {
addToFavourites(id:ID!): Boolean @invalidateCache(queries:["favouriteBooks"])
addBook(book:BookInput!): [Book] @invalidateCache(queries: ["books"])
}

6 . Caveats

There is one weird issue happening with @cacheControl directive, as it fails to set maxAge for some queries. To overcome that we've written a custom resolver for cacheController to make sure that value is always there

Conclusion:

By using ApolloServerPluginCacheControl for cache control metadata and responseCachePlugin to cache full responses, you can significantly improve the performance of your Apollo Server application. Storing the cache in Redis ensures a scalable, centralized cache that works across multiple instances, making it ideal for distributed systems.