Skip to content

Instantly share code, notes, and snippets.

@keeth keeth/api.tf
Last active Dec 20, 2018

Embed
What would you like to do?
Apex + Terraform + AWS Lambda + API Gateway + JSON Encoded Errors + CORS
resource "aws_api_gateway_rest_api" "myApi" {
name = "myApi-${var.env}"
description = "My awesome API (${var.env} environment)"
}
resource "aws_api_gateway_deployment" "myApi" {
depends_on = [
"aws_api_gateway_integration.myApi_myEndpoint_post",
"aws_api_gateway_integration_response.myApi_myEndpoint_post",
"aws_api_gateway_integration_response.myApi_myEndpoint_post_400",
"aws_api_gateway_method_response.myApi_myEndpoint_post_200",
"aws_api_gateway_method_response.myApi_myEndpoint_post_400",
"aws_api_gateway_integration.myApi_myEndpoint_options"
]
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
stage_name = "${var.env}"
}
resource "aws_api_gateway_resource" "myApi_myEndpoint" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
parent_id = "${aws_api_gateway_rest_api.myApi.root_resource_id}"
path_part = "myEndpoint"
}
resource "aws_api_gateway_method" "myApi_myEndpoint_post" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "POST"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "myApi_myEndpoint_post" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "${aws_api_gateway_method.myApi_myEndpoint_post.http_method}"
type = "AWS"
integration_http_method = "POST" # Must be POST for invoking Lambda function
credentials = "${var.gateway_lambda_role_arn}"
# http://docs.aws.amazon.com/apigateway/api-reference/resource/integration/#uri
uri = "arn:aws:apigateway:${var.aws_region}:lambda:path/2015-03-31/functions/${var.apex_function_myEndpoint}/invocations"
}
resource "aws_api_gateway_integration_response" "myApi_myEndpoint_post" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "${aws_api_gateway_method.myApi_myEndpoint_post.http_method}"
status_code = "${aws_api_gateway_method_response.myApi_myEndpoint_post_200.status_code}"
response_parameters_in_json = <<PARAMS
{
"method.response.header.Access-Control-Allow-Origin": "'*'"
}
PARAMS
}
resource "aws_api_gateway_integration_response" "myApi_myEndpoint_post_400" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "${aws_api_gateway_method.myApi_myEndpoint_post.http_method}"
status_code = "${aws_api_gateway_method_response.myApi_myEndpoint_post_400.status_code}"
selection_pattern = ".*message.*"
response_templates = {
"application/json" = <<EOT
#set ($errorMessageObj = $util.parseJson($input.path('$.errorMessage')))
{
"message" : "$errorMessageObj.message"
}
EOT
}
response_parameters_in_json = <<PARAMS
{
"method.response.header.Access-Control-Allow-Origin": "'*'"
}
PARAMS
}
resource "aws_api_gateway_method_response" "myApi_myEndpoint_post_200" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "${aws_api_gateway_method.myApi_myEndpoint_post.http_method}"
status_code = "200"
response_models = {
"application/json" = "Empty"
}
response_parameters_in_json = <<PARAMS
{
"method.response.header.Access-Control-Allow-Origin": true
}
PARAMS
}
resource "aws_api_gateway_method_response" "myApi_myEndpoint_post_400" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "${aws_api_gateway_method.myApi_myEndpoint_post.http_method}"
status_code = "400"
response_models = {
"application/json" = "Empty"
}
response_parameters_in_json = <<PARAMS
{
"method.response.header.Access-Control-Allow-Origin": true
}
PARAMS
}
# CORS
resource "aws_api_gateway_method" "myApi_myEndpoint_options" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "OPTIONS"
authorization = "NONE"
}
resource "aws_api_gateway_integration" "myApi_myEndpoint_options" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "${aws_api_gateway_method.myApi_myEndpoint_options.http_method}"
type = "MOCK"
request_templates = {
"application/json" = <<EOT
{"statusCode": 200}
EOT
}
}
resource "aws_api_gateway_integration_response" "myApi_myEndpoint_options_200" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "${aws_api_gateway_method.myApi_myEndpoint_options.http_method}"
status_code = "${aws_api_gateway_method_response.myApi_myEndpoint_options_200.status_code}"
response_parameters_in_json = <<PARAMS
{
"method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
"method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'",
"method.response.header.Access-Control-Allow-Origin": "'*'"
}
PARAMS
response_templates = {
"application/json" = <<EOT
{}
EOT
}
}
resource "aws_api_gateway_method_response" "myApi_myEndpoint_options_200" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myApi_myEndpoint.id}"
http_method = "${aws_api_gateway_method.myApi_myEndpoint_options.http_method}"
status_code = "200"
response_models = {
"application/json" = "Empty"
}
response_parameters_in_json = <<PARAMS
{
"method.response.header.Access-Control-Allow-Headers": true,
"method.response.header.Access-Control-Allow-Methods": true,
"method.response.header.Access-Control-Allow-Origin": true
}
PARAMS
}
// Forked from https://github.com/apex/node-apex
import R from 'ramda';
function serializeError(error) {
if (!R.is(Object, error)) {
return error;
}
const copy = R.merge({}, error);
if (typeof error.message === 'string') {
copy.message = error.message;
}
return copy;
}
function wrapCb(cb) {
return function(error, result) {
if (!R.isNil(error)) {
error = JSON.stringify(serializeError(error));
}
return cb(error, result);
}
}
export default function λ(fn) {
return function(e, ctx, cb) {
cb = wrapCb(cb);
try {
var v = fn(e, ctx, cb);
if (v && typeof v.then == 'function') {
v.then(function (val) {
cb(null, val);
}).catch(cb);
return;
}
cb(null, v);
} catch (err) {
cb(err);
}
}
};
@keeth

This comment has been minimized.

Copy link
Owner Author

commented Sep 2, 2016

The endpoint in this example expects an application/json POST request and returns an application/json response. Error responses (400) are also JSON.

My lambda function wrapper serializes any exceptions to JSON e.g. {"message": "Something went wrong"}, which is picked up by the 400 integration response, so that meaningful error objects can be returned rather than just strings.

Requires Terraform v0.6.16

Note that in Terraform v0.7 the response_parameters_in_json is superseded by response_parameters and you can use a regular Terraform map literals rather than the PARAMS heredoc strings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.