API Gateway

One of the most powerful ways to use AWS Lambda is to make function publicly available over HTTPS. This is accomplished by connecting the AWS Lambda function with the API Gateway. In this section we’ll start with a simple “echo” example and move on to a lambda function that accepts user parameters and returns an expiring S3 URL.

Concepts

Before moving on to the examples, it’s suggested you familiarize yourself with the API Gateway concepts.

The API Gateway presents a powerful and complex domain model. In brief, to integrate with the API Gateway, a service must:

  1. Define one or more AWS Lambda functions
  2. Create an API Gateway REST API instance
  3. Create one or more resources associated with the REST API
  4. Create one or more methods for each resource
  5. For each method:
    1. Define the method request params
    2. Define the integration request mapping
    3. Define the integration response mapping
    4. Define the method response mapping
  6. Create a stage for a REST API
  7. Deploy the given stage

See a the echo example for a complete version.

Request Types

AWS Lambda supports multiple function signatures. Some supported signatures include structured types, which are JSON un/marshalable structs that are automatically managed.

To simplify handling API Gateway requests, Sparta exposes the APIGatewayEnvelope type. This type provides an embeddable struct type whose fields and JSON serialization match up with the Velocity Template that’s applied to the incoming API Gateway request.

Embed the APIGatewayEnvelope type in your own lambda’s request type as in:

type FeedbackBody struct {
  Language string `json:"lang"`
  Comment  string `json:"comment"`
}

type FeedbackRequest struct {
  spartaEvents.APIGatewayEnvelope
  Body FeedbackBody `json:"body"`
}

Then accept your custom type in your lambda function as in:

func myLambdaFunction(ctx context.Context, apiGatewayRequest FeedbackRequest) (map[string]string, error) {
  language := apiGatewayRequest.Body.Language
  ...
}

Response Types

The API Gateway response mappings must make assumptions about the shape of the Lambda response. The default application/json mapping template is:

$input.json('$.body')
## Ok, parse the incoming map of headers
## and for each one, set the override header in the context.
## Ref: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference
## Ref: https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-override-request-response-parameters.html
#set($headers = $input.path("$.headers"))##
#foreach($key in $headers.keySet())##
#set($context.responseOverride.header[$key] = $headers[$key])##
#end##
## And use the code rather than integration templates so that
## the creation time is reduced
#if($input.path("$.code") != "")##
#set($context.responseOverride.status = $input.path("$.code"))##
#end##

This template assumes that your response type has the following JSON shape:

{
  "code" : int,
  "body" : {...},
  "headers": {...}
}

The apigateway.NewResponse constructor is a utility function to produce a canonical version of this response shape. Note that header keys must be lower-cased.

To return a different structure change the content-specific mapping templates defined by the IntegrationResponse. See the mapping template reference for more information.

Custom HTTP Headers

API Gateway supports returning custom HTTP headers whose values are extracted from your response. To return custom HTTP headers using the default VTL mappings, provide them as the optional third map[string]string argument to NewResponse as in:

func helloWorld(ctx context.Context,
  gatewayEvent spartaAWSEvents.APIGatewayRequest) (*spartaAPIGateway.Response, error) {

  logger, loggerOk := ctx.Value(sparta.ContextKeyLogger).(*logrus.Logger)
  if loggerOk {
    logger.Info("Hello world structured log message")
  }

  // Return a message, together with the incoming input...
  return spartaAPIGateway.NewResponse(http.StatusOK, &helloWorldResponse{
    Message: fmt.Sprintf("Hello world 🌏"),
    Request: gatewayEvent,
  },
    map[string]string{
      "X-Response": "Some-value",
    }), nil
}

Other Resources