Lessons learned working with API Gateway

Phil McCloghry-Laing
nib Travel Tech
Published in
5 min readJul 10, 2018

At World Nomads Group we’ve just taken our first foray into API Gateway using AWS’s Serverless Application Model (SAM). If you’re thinking about doing the same here’s some of the “gotchas” we encountered along the way and how we solved them.

A bit of background

We use Autopilot for our email marketing. We love Journeys and Autopilot has a powerful API that we’ve been hooking into based on customer interactions. We also use the Form Submitted Trigger heavily for campaigns, but we’ve found it’s not so reliable with our Single Page Applications (SPAs) and we wanted to change that. So, what to do?

While Autopilot has a great API, calling it directly from our web app would mean exposing an API key that would allow anyone to read and write customer data, so that’s a “No.” So we decided to build an API that would call Autopilot internally but would be write-only for a limited number of properties and Journeys. For something like this API Gateway + Lambda is a great fit, and the size of the API made it suitable as a proof-of-concept.

Gotcha #1: Webpack for NodeJS

We didn’t want to include all of node_modules in the Lambda ZIP file, so we used Webpack to bundle the Lambda into one tree-shaken JavaScript file (you could also use Parcel). In order to get Webpack working with TypeScript you need to add the ts-loader package to your project and associate it with *.ts files. However, that’s not the only configuration required. The first major stumbling block was working out why our Lambda wouldn’t execute. It turns out that the default settings for Webpack are for a browser environment and don’t play nicely with NodeJS. It’s a simple fix, just specify target: 'node' in the Webpack config file.

Webpack configuration for TypeScript + NodeJS

Lambda & async/await

While you can compile TypeScript to target older environments, async/await is available in NodeJS 8.10, which is one of the runtimes supported by AWS Lambda (it also supports 6.10). Check node.green to see NodeJS support for other ECMAScript features.

Gotcha #2: Understanding API Gateway Integrations

When dealing with API Gateway you need to think about the API Gateway request/response as separate to the integration request/response. A request to API Gateway gets transformed into an integration request and when responding the integration response gets transformed into an API Gateway response. All requests to Lambdas need to be made using the POST method (regardless of what you’re accepting through API Gateway), so don’t try integrating with your Lambda using GET or PUT. If you use aws_proxy (which I recommend) API Gateway will send an event object to the Lambda with the original HTTP Method in it (as well as lots of other useful information) and won’t require request/response transformation.

Keep in mind that you can integrate with all sorts of services other than Lambda. We built a /docs/ endpoint where the integration is a simple GET to S3, where we upload our Swagger UI documentation. You can also upload or delete from S3 using PUT and DELETE requests.

Gotcha #3: Lambda permissions

The other reason the Lambda wouldn’t execute is Lambda Permissions. When using Swagger configuration API Gateway doesn’t have the necessary permissions to actually invoke any Lambdas. You need to add a Lambda Permission resource for each Lambda, giving API Gateway permission to execute that particular Lambda.

Example snippet of SAM YAML configuration for Lambda Permissions

Gotcha #4: Swagger and DefinitionUri vs DefinitionBody

We’ve been using Swagger to create documentation for our .NET APIs and it made sense to do the same with API Gateway. The hard bit was how to keep the Swagger configuration separate from the SAM configuration so we could use it to generate documentation. The solution should be DefinitionUri. Unfortunately it just doesn’t seem to be suitable at this stage. It doesn’t work with local files and importing variables like Lambda ARNs using Stage Variables doesn’t seem to work. We did manage to keep the swagger file separate, but it required specifying DefinitionBody using Fn::Transform and using the long form for AWS functions (Fn::Sub instead of !Sub) in swagger.yml. The Swagger file also needs to be uploaded to S3.

Example snippets of separate SAM and swagger YAML files

A note on throttling & logging

Throttling and logging configuration are part of MethodSettings within a Stage resource. Stage is one of the resources that’s created automatically for us when using SAM, but you can specify MethodSettings on the AWS::Serverless::Api resource and it will be applied to the stage.

Example snippet of SAM YAML configuration with throttling & logging configuration

Gotch #5: Cloudflare

We found this out the hard way, but if you’re migrating to Cloudflare it simplifies things if you import your own SSL certificate — that way your sites aren’t broken while you wait for Cloudflare to authenticate your domain and generate a certificate.

We generated a wildcard origin certificate (e.g. *.example.com) so that we can create APIs for different subdomains with the one certificate. This involved generating the certificate in the Cloudflare dashboard (Crypto tab → Origin Certificates). To import it into AWS Certificate Manager you will need to copy the certificate, private key and the appropriate Cloudflare root certificate authority into the Certificate body, Certificate private key and Certificate chain fields, respectively. API Gateway requires all certificates to be in the us-east-1 region, so make sure you upload your certificate there.

In order to avoid having to maintain the Certificate ARN in our deployment configuration we use the list-certificates command from the AWS CLI and jq to fetch the correct Certificate ARN (in Bash):

CERTIFICATE_ARN=$(aws acm --region us-east-1 list-certificates | jq -r ".CertificateSummaryList[] | select(.DomainName == \"$DOMAIN_NAME\") | .CertificateArn")

In Cloudflare you’ll need to add a CNAME entry to your DNS for the particular sub-domain and point it to the Cloudfront URL for your Custom Domain (can be found in the AWS Console under AWS GatewayCustom Domain NamesTarget Domain Name).

Conclusion

SAM saved us worrying about a lot of configuration (no Deployment and Stage resources to set up), but there are still some rough edges. It feels like the Swagger documentation should work with Stage Variables — that’s something I’d like us to look into further and hopefully get working.

While I mentioned CloudWatch logging, we actual log to Sumo Logic via an SNS Queue. This works fine within our Lambda functions but we may need to work out a different solution if we want to monitor other integrations, like S3.

I think this is something we could start using extensively to develop our ChatOps. We have a number of useful integrations in Slack, like build and deployment notifications, but the next stage is making those interactive — API Gateway will make setting those up faster and simpler.

We’re going to be evaluating API Gateway for one of our upcoming pieces of work, localised product configuration for World Nomads, but I’m definitely keen to take what we’ve learnt here and move more of our (growing) micro-service APIs to API Gateway.

--

--