# Caching

If some fields in your schema are expensive or slow to compute, it can be beneficial to cache their result. Use the @cache directive to instruct Lighthouse to cache the result of a resolver.

The cache is created on the first request and is cached forever by default. Use this for values that seldom change and take long to fetch/compute.

type Query {
  highestKnownPrimeNumber: Int! @cache
}

Set an expiration time to invalidate the cache after the given number of seconds.

type Query {
  temperature: Int! @cache(maxAge: 300)
}

You can limit the cache to the authenticated user making the request by marking it as private. This makes sense for data that is specific to a certain user.

type Query {
  todos: [ToDo!]! @cache(private: true)
}

# Clear cache

To enable this feature, you need to use a cache store that supports cache tags (opens new window) and enable cache tags in config/lighthouse.php:

    /*
    |--------------------------------------------------------------------------
    | Cache Directive Tags
    |--------------------------------------------------------------------------
    |
    | Should the `@cache` directive use a tagged cache?
    |
    */
    'cache_directive_tags' => true,

Now, you can place the @clearCache directive on mutation fields. When they are queried, they will invalidate all cache entries associated with a calculated tag. Depending on the effect of the mutation, you can clear different tags.

Update the cache associated with a given type without a specific ID:

This does not invalidate cache entries related to the type and ID.

type Mutation {
  updateSiteStatistics(input: SiteInput!): Site @clearCache(type: "Site")
}

If your mutation affects only a certain ID, specify the source for that ID:

type Mutation {
  updatePost(input: UpdatePostInput!): Post!
    @clearCache(type: "Post", idSource: { argument: "input.id" })
}

If your mutation affects multiple entities, you can use the result as the source of IDs:

type Mutation {
  updatePosts(search: String!, newValue: Int!): [Post!]!
    @clearCache(type: "Post", idSource: { field: "*.id" })
}

If your mutation only affects a single field, you can clear tags that are specific for that:

type Mutation {
  updatePost(id: ID!, title: String!): Post!
    @clearCache(type: "Post", idSource: { argument: "id" }, field: "title")
}

If your mutation affects multiple levels of cache, you can apply this directive repeatedly.

# Cache key

When generating a cached result for a resolver, Lighthouse produces a unique key for each type. By default, Lighthouse will look for a field of type ID on the parent to generate the key for a field with @cache.

This directive allows to use a different field (i.e., an external API id):

type GithubProfile {
  username: String @cacheKey
  repos: [Repository] @cache
}

# Implementing your own cache key generator

In one of your application service providers, bind the Nuwave\Lighthouse\Cache\CacheKeyAndTags.php (opens new window) interface to your cache key generator class:

$this->app->bind(CacheKeyAndTags::class, YourOwnCacheKeyGenerator::class);

You can extend Nuwave\Lighthouse\Cache\CacheKeyAndTagsGenerator.php (opens new window) to override certain methods, or implement the interface from scratch.

# HTTP Cache-Control header

Experimental: not enabled by default, not guaranteed to be stable.

Add the service provider to your config/app.php:

'providers' => [
    \Nuwave\Lighthouse\CacheControl\CacheControlServiceProvider::class,
],

You can change the Cache-Control header (opens new window) of your response regardless of @cache by adding the @cacheControl directive to a field. The directive can be defined on the field-level or type-level. Note that field-level settings override type-level settings.

The final header settings are calculated based on these rules:

  • max-age equals the lowest maxAge among all fields. If that value is 0, no-cache is used instead
  • visibility is public unless the scope of a queried field is PRIVATE

The following defaults apply:

  • non-scalar fields maxAge is 0
  • root fields maxAge is 0 and scope is PRIVATE
  • the directive default is prior to the field default

For more details check Apollo (opens new window).

Given the following example schema:

type User {
  tasks: [Task!]! @hasMany @cacheControl(maxAge: 50, scope: PUBLIC)
}

type Company @cacheControl(maxAge: 40, scope: PUBLIC) {
  users: [User!]! @hasMany @cacheControl(maxAge: 25, scope: PUBLIC)
}

type Task {
  id: ID @cacheControl(maxAge: 10, scope: PUBLIC)
  name: String @cacheControl(maxAge: 0, inheritMaxAge: true)
  description: String @cacheControl
}

type Query {
  me: User! @auth @cacheControl(maxAge: 5, scope: PRIVATE)
  companies: [Company!]!
  publicCompanies: [Company!]! @cacheControl(maxAge: 15)
}

The Cache-Control headers for some queries will be:

# Cache-Control header: max-age: 5, PRIVATE
{
  # 5, PRIVATE
  me {
    # 50, PUBLIC
    tasks {
      # 10, PUBLIC
      id
    }
  }
}

# Cache-Control header: no-cache, PRIVATE
{
  # 5, PRIVATE
  me {
    # 50, PUBLIC
    tasks {
      # 0, PUBLIC
      description
    }
  }
}

# Cache-Control header: no-cache, private
{
  # 40, PUBLIC
  companies {
    # 25, PUBLIC
    users {
      # 50, PUBLIC
      tasks {
        # 10, PUBLIC
        id
      }
    }
  }
}

# Cache-Control header: maxAge: 10, public
{
  # 15, PUBLIC
  publicCompanies {
    # 25, PUBLIC
    users {
      # 50, PUBLIC
      tasks {
        # 10, PUBLIC
        id
      }
    }
  }
}

# Cache-Control header: maxAge: 15, public
{
  # 15, PUBLIC
  publicCompanies {
    # 25, PUBLIC
    users {
      # 50, PUBLIC
      tasks {
        # 50, PUBLIC
        name
      }
    }
  }
}