blog-banner

AWS API Gateway - Request Response Mapping

  • AWS API GATEWAY
  • REQUEST MAPPING
  • VTL

API Gateway Response Mapping

AWS API Gateway is a very useful service to put a proxy in front of your backend API, or even have a Lambda function without any backend endpoints. It does support a lot of AWS services. In this blog, I am going to talk about how you can do body mapping for the input request of the response coming back from the backend endpoint. It uses the VTL language.

If you wonder how this will be useful, it could cut down the unnecessary endpoint for doing that job. Let’s say you have a search autocomplete feature where you need to receive GET requests and then form that input into a complex input for our search engine (ElasticSearch or SOLR or CloudSearch) like removing certain words and building a custom query with additional conditions. You will need intermediate processing that does it for you to say the Lambda function. Instead of that, you can use the Mapping feature of API Gateway to do the conversion. Let's see how to do that.

Step 1: Build a new endpoint with GET method in API Gateway.

Step 2: Map the input request parameter, eg below

Step 3: Go to Integrations request->Body Mapping Templates

I choose “Never” to indicate that the request should always be transformed, else do not pass it. Now click the “application/json” or create a new Content Type mapping, now comes the important part, where you need to write VTL code to do the processing.

Request Mapping Code

## Set the variable inputRoot to the root JSON document

{

#set ($aptparams = ['APT','LOT'])

#set ($address = $input.params("address").toUpperCase())

#set ($address = $address.replaceAll(" +", " "))

#set ($address = $address.replace(".", ""))

#set ($address = $address.replace(",", " "))

#set ($replace = 0)

#set ($myStopWords = {

"LN" : "LANE",

'ROAD':'RD',

'STREET':'ST',

'DRIVE ':'DR',

})



##$address.replaceAll("LN\s*$", " ")

#foreach($stopKey in $myStopWords.keySet())

   #set($replacement = $myStopWords.get($stopKey))

   #set($address = $address.replace(" $stopKey ", " "))

   #set($address = $address.replaceAll("$stopKey\s*$", ""))

   #set($address = $address.replace($replacement, " "))

   #set($address = $address.replaceAll("$replacement\s*$", ""))

#end

#foreach($apt in $aptparams)

   #set($address = $address.replace(" $apt ", " "))

   #set($address = $address.replaceAll("$apt\s*$", ""))

#end

#set ($address = $address.toUpperCase())



"query": {

 "bool": {

   "must": {

     "match_phrase": {

       "address": {"query":"$address","slop":6}

     }

   },

    "must_not": {

     "match": {

       "status": "Inactive"

     }

   },

   "filter": {

     "term": {

       "zip": "$input.params('zip')"

     }

   }

  }

}

}



$input.params("address") gets the GET param from the original request. VTL supports various string functions like,

  • toUpperCase
  • toLowerCase
  • contains
  • equals

From the code I have posted above you could see how to use it for loop, setting variables, and comment.

Some tips,

  1. ## means comment

  2. # is a command

  3. For each or it should end with #end

Remember not all the VTL constructs are supported in AWS API Gateway, for eg see the response I received from the AWS Support team for using .isNumber(),

API Gateway doesn't support all the constructs of VTL. It only supports a very specific set of constructs that are provided in VTL. So methods like "$String.isNumber()", "$Integer.parseInt()" etc., are not supported by API Gateway.

VTL Guide: https://velocity.apache.org/engine/1.7/user-guide.html

Step 4: You could also map the response coming back from the backend API.

Now click the Content-Type required to write your VTL code the processing. See the sample I have written,

#set($hits=$input.json("$.hits.hits"))

[

#foreach ($hit in $util.parseJson($hits))

{

  #set($hitData=$hit.get('_source'))

  #foreach($paramName in $hitData.keySet())

     "$paramName" : "$util.escapeJavaScript($hitData.get($paramName))"

     #if($foreach.hasNext),#end

   #end

  

}#if($foreach.hasNext),#end    

#end

]

I receive the “hits” inside the “hits” object of the original response we receive as input. Then loop over the records, get the _source object inside each record, and build a new JSON output.

This is needed to format the ElasticSearch search response to be easily accessible by the client libraries. That's all, your input and output are preprocessed now. You can use the "Test" feature of API Gateway to debug how the mapping works. Let me know if you run into any trouble.