How to set Apollo's cacheControl Directive in Nestjs code-first
Asked Answered
S

1

7

I am trying to set a static cacheControl on some fields, as done here

From my understanding, I need to use a directive, so I used the following nest documentation to declare directives

So, I built a cacheControl directive, this is what my GraphQLModule.forRootAsync has in the buildSchemaOptions:

buildSchemaOptions: {
        directives: [
          new GraphQLDirective({
            name: 'cacheControl',
            locations: [
              DirectiveLocation.FIELD_DEFINITION,
              DirectiveLocation.OBJECT,
              DirectiveLocation.INTERFACE,
              DirectiveLocation.UNION
            ],
            args: {
              maxAge: { type: GraphQLInt },
              scope: {
                type: new GraphQLEnumType({
                  name: 'CacheControlScope',
                  values: {
                    PUBLIC: {
                      astNode: {
                        kind: 'EnumValueDefinition',
                        description: undefined,
                        name: {
                          kind: 'Name',
                          value: 'PUBLIC'
                        },
                        directives: []
                      }
                    },
                    PRIVATE: {
                      astNode: {
                        kind: 'EnumValueDefinition',
                        description: undefined,
                        name: {
                          kind: 'Name',
                          value: 'PRIVATE'
                        },
                        directives: []
                      }
                    }
                  }
                })
              },
              inheritMaxAge: { type: GraphQLBoolean }
            }
          })
        ]
      } 

And it did create the directive in the schema:

directive @cacheControl(maxAge: Int, scope: CacheControlScope, inheritMaxAge: Boolean) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION

enum CacheControlScope {
  PUBLIC
  PRIVATE
}

Now, I try to use it on my field declaration in my @ObjectType like so:

import { Directive, Field, Int, ObjectType } from '@nestjs/graphql'

@ObjectType('User')
export class User {
  @Directive('@cacheControl(maxAge:60)')
  @Field(() => Int)
  id!: number

  @Directive('@cacheControl(maxAge:60)')
  @Field()
  text!: string
}

But when doing some query to get all users, it does not seem to cache anything - and does not send a cache-control header and does the whole query each time. I tried to do a transformer, but not sure how to implement caching for the resolvers. What am I missing?

Shaum answered 20/6, 2022 at 7:15 Comment(4)
Did you find an answer to this problem?Singband
Unfortunately no...Shaum
I think the transformer is important because the resulting GQL schema needs to be updated at build time. I also tried to just add the buildSchemaOptions as you metionned but the resulting schema does not have any @cacheControl directive. That's why I think the transformer is important. I'll try.Singband
From the doc : Directives applied through the @Directive() decorator will not be reflected in the generated schema definition file. This means that the generated schema will not contain any reference to @cacheControl directive.Singband
P
1

I was able to get this working in NestJS. The key was to make sure that any child object types had either also specified a maxAge or had inheritMaxAge: true set. This was the same for the resolver Query and ResolveField.

Code:

parent.resolver.ts

import { Field, ObjectType, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql';

import { CacheControl } from './cache-control.decorator';

// cache control hints can be applied to types
@CacheControl({ maxAge: 100 })
@ObjectType()
export class ParentSchema {
    @Field()
    foo: string;
}

// need to make sure child types also specify cache control hints
@CacheControl({ inheritMaxAge: true })
@ObjectType()
export class ChildSchema {
    @Field()
    bar: string;
}

@Resolver(() => ParentSchema)
export class ParentResolver {
    // OR cache control hints can be applied to resolvers
    @CacheControl({ maxAge: 100 })
    @Query(() => Parent, { name: 'parent' })
    async getParent(): Promise<ParentSchema> {
        return { foo: 'biz' };
    }

    // need to make sure that the child field also specifies cache control hints
    @CacheControl({ inheritMaxAge: true })
    @ResolveField('child', () => ChildSchema)
    async getChild(): Promise<ChildSchema> {
        return { bar: 'baz' };
    }
}

app.module.ts

import { ApolloServerPluginCacheControl } from '@apollo/server/plugin/cacheControl';
import responseCachePlugin from '@apollo/server-plugin-response-cache';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule, Int } from '@nestjs/graphql';
import { DirectiveLocation, GraphQLBoolean, GraphQLDirective, GraphQLEnumType } from 'graphql';

import { ParentResolver } from './parent.resolver';

@Module({
    imports: [
        GraphQLModule.forRoot<ApolloDriverConfig>({
            driver: ApolloDriver,
            autoSchemaFile: true,
            plugins: [ApolloServerPluginCacheControl(), responseCachePlugin()],
            buildSchemaOptions: {
                directives: [
                    new GraphQLDirective({
                        name: 'cacheControl',
                        args: {
                            maxAge: { type: Int },
                            scope: {
                                type: new GraphQLEnumType({
                                    name: 'CacheControlScope',
                                    values: {
                                        PUBLIC: {},
                                        PRIVATE: {},
                                    },
                                }),
                            },
                            inheritMaxAge: { type: GraphQLBoolean },
                        },
                        locations: [
                            DirectiveLocation.FIELD_DEFINITION,
                            DirectiveLocation.OBJECT,
                            DirectiveLocation.INTERFACE,
                            DirectiveLocation.UNION,
                            DirectiveLocation.QUERY,
                        ],
                    }),
                ],
            },
        }),
    ],
    providers: [ParentResolver]
})
export class AppModule {}

cache-control.decorator.ts

import { Directive } from '@nestjs/graphql';

interface CacheControlOptions {
    maxAge?: number;
    scope?: 'PRIVATE' | 'PUBLIC';
    inheritMaxAge?: boolean;
}

export const CacheControl = ({ maxAge, scope = 'PUBLIC', inheritMaxAge }: CacheControlOptions) => {
    const args = [
        `scope: ${scope}`,
        maxAge !== undefined ? `maxAge: ${maxAge}` : null,
        inheritMaxAge ? `inheritMaxAge: ${inheritMaxAge}` : null,
    ]
        .filter(Boolean)
        .join(', ');

    return Directive(`@cacheControl(${args})`);
};

Pinette answered 14/3 at 17:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.