API Gateway caching with AWS CDK

June 05, 2020

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. So, if we want to have a

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 Twitter.

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,
      },
    });
  }
}

Feel free to send feedback or ask questions on Twitter.



Written by Antoine Lehurt (a.k.a kewah). Senior front-end engineer living in Stockholm and currently working at Acast.