Search⌘ K
AI Features

Request Body

Explore how to handle complex request bodies in AWS API Gateway by leveraging Velocity templates for data transformation. Understand the use of the $context variable, programming constructs like #set and #foreach, and how to implement mappings on the AWS console. This lesson helps you build efficient APIs without extra latency or cost by avoiding unnecessary Lambda functions.

Payload mapping

Query strings and path parameters are pretty simple to isolate and transform. However, the complexity increases exponentially when we look into the API payload. The request body could be a complex JSON, XML, or just a blob. It’s challenging to extract details from it and transform them per the target API's requirements. Let’s now dig deeper to understand the immense potential of this feature.

API Gateway simplifies this process using Apache Velocity templates, which provide a concise syntax for data transformation. The templates are intuitive, learnable simply by reading a few lines of code.

Velocity template example

Let’s check out an example where we use a Velocity template to construct a payload of the API integration. The below code uses an HTTP integration with a public API for IP geolocation. The API returns the geolocation for any IP address in the input payload. The integration pulls the source IP address from the request and passes it on to the public API to get the geolocation of the server running this code.

#!/bin/sh -v

# -----------------------------------------------------------------
# Configure the AWS CLI to let it communicate with your account
# -----------------------------------------------------------------
aws configure set aws_access_key_id $aws_access_key_id
aws configure set aws_secret_access_key $aws_secret_access_key
aws configure set region us-east-1


# -----------------------------------------------------------------
# Delete any old deployments
# -----------------------------------------------------------------
# 1. Trigger CloudFormation stack delete
# 2. Wait for the stack to be deleted 
aws cloudformation delete-stack --stack-name  EducativeCourseApiGateway
aws cloudformation wait stack-delete-complete --stack-name EducativeCourseApiGateway


# -----------------------------------------------------------------
# With everything ready, we initiate the CloudFormation deployment.
# -----------------------------------------------------------------
aws cloudformation deploy \
    --template-file template.yml \
    --stack-name EducativeCourseApiGateway \
    --capabilities CAPABILITY_NAMED_IAM \
    --region us-east-1

# -----------------------------------------------------------------
# Get the API ID of the Rest API we just created.
# -----------------------------------------------------------------
apiId=`aws cloudformation list-stack-resources --stack-name EducativeCourseApiGateway | jq -r ".StackResourceSummaries[1].PhysicalResourceId"`
echo "API ID: $apiId"

# -----------------------------------------------------------------
# This is the URL for the API we just created
# -----------------------------------------------------------------
url="https://${apiId}.execute-api.us-east-1.amazonaws.com/v1/whereami"
echo $url

# -----------------------------------------------------------------
# Invoke the URL to test the response
# -----------------------------------------------------------------
curl --location --request POST $url --header 'Content-Type: application/json' --data-raw '{  }' | jq 

Mapping with Velocity template

Click "Run" to deploy and test the API. Once the new API is deployed, the script invokes this API to get the expected outcome, that is, the geolocation details of the server where this script runs.

Let’s observe how this works.

  • Lines 33 and 34 in template.yml define the HTTP integration with a public API, https://public.krazyminds.com/ipinfo

  • Lines 38 and 39 in template.yml define how the payload’s body is constructed. It maps the $context.identity.sourceIp to the ip field in the request payload.

The target API, https://public.krazyminds.com, returns the geolocation based on this IP address. So, we have an API that returns the geolocation of the caller. Such an API has several applications in different products.

Complex mappings

That’s the power of Velocity templates. A tiny script could give us a useful API. Velocity templates have much more power than we saw in the example above.

Check out the script below. It’s thoroughly commented, so you can easily read through it to determine what it’s doing. First, it gets all the path parameters, query parameters, and request headers. Next, it processes the input JSON payload, parses it, and adds it to the bigger input payload. Finally, it fetches a few fields from the API context. This tiny little script can pull out much information from the API request.

The code below has detailed comments. A quick read through should clarify the structure of a Velocity template. In short, it provides some programming constructs like #set, #foreach, #if, and so on. The ## defines a comment that the data mapper doesn’t process. Anything apart from these is part of the output. Finally, $ defines a variable and maps its values in the final output.

C++
## This template will pass through all parameters including path, querystring, header, stage variables, and context through to the integration endpoint via the body/payload
## This creates a new variable $allParams with value $input.params() - all the parameters in the input - path parameters as well as query parameters.
#set($allParams = $input.params())
## We want to create a JSON output. This is the starting bracket.
{
## First field in the JSON object. The body of the input request is a JSON. So cast it and assign to the "body-json" field
"body-json" : $input.json('$'),
## Next field in the output JSON is the collection of parameters.
"params" : {
## To get this, we loop over the allParams variable we defined above.
#foreach($type in $allParams.keySet())
## These parameters could be nested. So go one level into it and process the individual parameters
#set($params = $allParams.get($type))
"$type" : {
#foreach($paramName in $params.keySet())
## After second level, assign the value as is. If the parameter is nested even deeper, stringify that JSON and assign the value.
"$paramName" : "$util.escapeJavaScript($params.get($paramName))"
#if($foreach.hasNext),#end
#end
}
#if($foreach.hasNext),#end
#end
},
## now look for the context variables provided by API gateway.
"context" : {
## The API gateway ID
"api-id" : "$context.apiId",
## The HTTP method used for this API
"http-method" : "$context.httpMethod",
## The IP address of the client
"source-ip" : "$context.identity.sourceIp"
}
}

$context

The $context object is a special system variable containing information about the API invocation context. It contains a lot of handy information that can help the decisions on the backend. The table below, extracted from AWS documentation, provides a detailed list of fields in this $context.

We can use them in the Velocity templates to get the required data. Below are some of the important fields in $context. Look at the AWS documentation for a complete list.

Contents of $context

Parameter

Description

$context.accountId

This is the API owner's AWS account ID.

$context.apiId

API Gateway provides an identifier to each API. The value of this identifier can be found in this field.

$context.identity.apiKey

If the API was called with an API key, the key can be found in this field. If there’s no API key, the value of this field is null.

$context.identity.sourceIp

This is the source IP address of the immediate TCP connection that makes the request to the API Gateway endpoint.

$context.identity.userAgent

This is the user-agent header of the API caller. This is defined by the client application. It could be the browser info or, if it’s a custom client, the client can identify itself with a custom user agent name.

$context.requestId

This is an ID for the request. Clients can override this request ID. Use $context.extendedRequestId for a unique request ID that API Gateway generates.

$context.requestTime

This is the CLF-formatted request time (dd/MMM/yyyy:HH:mm:ss +-hhmm).

$context.requestTimeEpoch

This is the epoch-formatted request time, in milliseconds.

Web console

Let’s now look at how this works on the AWS console. First, open the API Gateway console in a browser and navigate to EducativeRestAPI. Next, click the “Integration Request” link and scroll down to the “Mapping Templates.”

The Velocity template we added in template.yml lands here in the mapping templates.

Velocity templates provide a powerful tool to transform and process the API. Developers often skip this power and use a Lambda function to do this computation. However, using a Lambda function incurs additional latency and cost. When we use Velocity templates in the API Gateway, we don’t have any additional cost and there’s almost no additional latency.

Always ask yourself if you need to integrate a Lambda function, or if it’s possible, rather, to do the same using the Velocity templates.