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.
We'll cover the following...
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
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.ymldefine the HTTP integration with a public API,https://public.krazyminds.com/ipinfoLines 38 and 39 in
template.ymldefine how the payload’s body is constructed. It maps the$context.identity.sourceIpto theipfield 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.
$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.