Skip to Content

Cache Rules

The Stellate Edge Cache gives you fine-grained per-type or even per-field control over your cache behavior for every single query that is sent through your service with cache rules.

Basics

A basic rule to cache all queries that contain any User could look like this:

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    rules: [
      {
        types: ['User'],
        maxAge: 900,
        swr: 900,
        scope: 'AUTHENTICATED',
        description: 'Cache Users',
      },
    ],
  },
}
export default config

A rule applies to all queries that contain the rule’s types or fields. If multiple rules apply to the same query, Stellate will use the minimum maxAge, the minimum swr, and combine all scopes to cache the query result.

Introspection Query

Caching the introspection query is a special case, which is not handled via regular cache rules. Instead, you can enable caching by setting cacheIntrospection to true in your service configuration. By default responses to the introspection query are cached with a maxAge of 63 minutes, as well as an swr of 1 day. Those are currently not configurable via the configuration file.

If your service has ignoreOriginCacheControl turned off, Stellate will respect the Cache-Control headers present on the response to the introspection query and cache the response accordingly.

To invalidate cached introspection results, please use the CLI to push a new schema version.

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    cacheIntrospection: true,
  },
}
export default config

An extra origin cache rule

Cache-Control headers will always be ignored if you have Partial Query Caching enabled. For PQC to properly work, Cache-Control headers set by your origin can’t be taken into account.

When turning off ignoreOriginCacheControl explicitly in your config we will treat the cache-control header returned by your origin as an additional cache-rule.

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    ignoreOriginCacheControl: false,
    rules: [
      {
        types: ['Post'],
        maxAge: 900,
        swr: 900,
        description: 'Cache Posts publicly',
      },
      {
        types: ['User'],
        maxAge: 450,
        swr: 1000,
        scope: 'AUTHENTICATED',
        description: 'Cache User data privately',
      },
    ],
  },
}
export default config

This means that after we calculate the max-age and stale-while-revalidate based on your rules we will compare them to the values we get from your cache-control and take the lowest of the two. Note that s-maxage will take presedence over max-age when it comes to CDN-caching.

We take the rules of a shared cache in account, this means that when we see an authorization header we require the mention of public, s-maxage or must-revalidate to take the origin cache-control in account.

When we see any of the uncacheable directives like no-cache, no-store or private we will treat this as a cache-pass.

Configuration

types (required)

The rule will apply its cache configuration to any query that contains the types or fields mentioned in the types property.

You can target both types as well as specific fields of types:

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    rules: [
      {
        types: {
          // This rule will apply to any query that contains any User
          User: true,
 
          // This rule will apply to any query that contains Post.comments
          Post: ['comments'],
 
          // This rule will apply to any "{ drafts }" query
          // (assuming the "Query" type is the root schema query type)
          Query: ['drafts'],
        },
      },
    ],
  },
}
export default config

maxAge (seconds, optional)

The maxAge parameter configures how long query results that match the rule types should be cached, in seconds. If multiple rules apply to the same query, the minimum maxAge will be used for the entire query result.

Setting maxAge: 0 is a way of preventing any types and fields that match the rule from ever be cached. However, we recommend using the nonCacheable config property for that purpose instead.

swr (stale-while-revalidate, seconds, optional)

The “swr” (stale-while-revalidate) property configures how long stale query results that match the rule types should be served while fresh data is already being fetched in the background.

For example, if you set the maxAge of a query to 60s and swr to 300s, depending on when new requests come in the following actions are taken:

  1. Within the first 60 seconds, the cached result will be returned from our edge locations.
  2. From 60s to 360s we will pass on the first request to your backend service, but continue to serve the cached (but stale) results for other identical requests. As soon as we get an updated response from your backend service we will update the cached data.
  3. Any requests coming in later than 360s will get passed on to your backend service. The first response will re-populate the cache.

We would recommend always configuring stale-while-revalidate, as well as keeping the swr limits fairly high. Your users will get a very quick response to their request, while we load potentially updated data in the background and update the cache.

If you want to read more about this topic, please see Keeping things fresh with stale-while-revalidate by Jeff Posnick.

scope (string, optional)

Which scope to apply to query results that match the rule types. See the documentation on Scopes for more information on setting up scopes.

description (string, optional)

Description of the purpose of the rule in plain English.

FAQ

How do I cache every GraphQL query?

To apply a rule to everything, target the root Query type. This might look like the following example, if you did not rename that type.

Since the Query type is included in every single query, this rule will apply to all queries. Note that creating a rule that targets the Query type sets a maximum maxAge and swr for all rules! Since Stellate always uses the minimum maxAge and swr of all the rules that apply to a single query, and this rule applies to all queries, you can never increase the maxAge and swr of any cached query result to more than 900 (in this example).

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    rules: [
      {
        types: ['Query'],
        maxAge: 900,
        swr: 900,
        description: 'Cache everything',
      },
    ],
  },
}
export default config

How to not cache a type?

If you want to make sure a specific type or field is not getting cached at all, add its name to the nonCacheable config property.

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    nonCacheable: [
      // Don't cache the `User` type
      'User',
      // Don't cache the field `comments` on the `Post` type
      'Post.comments',
    ],
  },
}
export default config

What happens when multiple rules apply to the same GraphQL query?

Stellate will use the minimum maxAge, and the minimum swr and combine all scopes to cache the query result.

For example, imagine you have these two rules defined:

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    rules: [
      {
        types: ['Post'],
        maxAge: 900,
        swr: 900,
        description: 'Cache Posts publicly',
      },
      {
        types: ['User'],
        maxAge: 450,
        swr: 1000,
        scope: 'AUTHENTICATED',
        description: 'Cache User data privately',
      },
    ],
  },
}
export default config

And send a query that contains a User and a Post, which means both rules apply:

{
  user(id: "...") {
    id
    posts(first: 10) {
      id
    }
  }
}

Then the query result would be cached with a 450s maxAge (the minimum of both rules), a 900s swr (the minimum of both rules) as well as with a scope of AUTHENTICATED (the combination of all the scopes applied via the rules).

Last updated on