Antoine Lehurt

CDK pattern – API Gateway caching

API Gateway has built-in support for caching endpoint’s responses. So, we don’t have to set up CloudFront by ourselves.

Enable API Gateway caching

To enable caching, we need to use an edge optimized endpoint and set some values on the deployOptions.

const api = new apigateway.RestApi(this, id, {
  domainName: {
    endpointType: apigateway.EndpointType.EDGE,
  },
  deployOptions: {
    cachingEnabled: true,
    cacheClusterEnabled: true,
    cacheTtl: cdk.Duration.minutes(30),
  },
});

Note that if we need to use a custom domain for our API the certificate needs to be in the us-east-1.

To use an ACM certificate with an API Gateway edge-optimized custom domain name, you must request or import the certificate in the us-east-1 Region (US East (N. Virginia)). To enable caching with need to use an edge optimized endpoint.

We can now query our endpoint and check CloudWatch to check that we are hitting the cache.

CloudWatch shows us that we are hitting the cache

Query string support

That works great until we start to use query strings. For instance, if we build an OEmbed API, we need to support url and format query strings. With the current setup, we will always get the same cached response when hitting the endpoint even if we change the url value.

To fix that, we need to set up the request parameters to take the query strings into account.

const integration = new apigateway.LambdaIntegration(lambda, {
  cacheKeyParameters: ['method.request.path.url', 'method.request.path.format'],
  requestParameters: {
    'integration.request.path.url': 'method.request.path.url',
    'integration.request.path.format': 'method.request.path.format',
  },
});
endpoint.addMethod('GET', integration, {
  requestParameters: {
    'method.request.path.url': true,
    'method.request.path.format': true,
  },
});

If you know a less verbose solution, please don’t hesitate to ping me on Mastodon.

All together solution

export class Stack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const lambda = new lambda.Function(this, 'SomeFunctionId', {
      runtime: lambda.Runtime.NODEJS_12_X,
      code: lambda.Code.fromAsset('functions/dist/handler'),
      handler: 'index.handler',
    });

    const api = new apigateway.RestApi(this, id, {
      domainName: {
        endpointType: apigateway.EndpointType.EDGE,
      },
      deployOptions: {
        cachingEnabled: true,
        cacheClusterEnabled: true,
        cacheTtl: cdk.Duration.minutes(30),
      },
    });
    const v1 = api.root.addResource('v1');
    const endpoint = v1.addResource('some-route');

    const integration = new apigateway.LambdaIntegration(lambda, {
      cacheKeyParameters: ['method.request.path.url', 'method.request.path.format'],
      requestParameters: {
        'integration.request.path.url': 'method.request.path.url',
        'integration.request.path.format': 'method.request.path.format',
      },
    });
    endpoint.addMethod('GET', integration, {
      requestParameters: {
        'method.request.path.url': true,
        'method.request.path.format': true,
      },
    });
  }
}