Check out bidbear.io Amazon Advertising for Humans. Now publicly available 🚀

AWS API Gateway: Mapping Template Cheatsheet

Intro

While API Gateway makes it extremely simple to build routes and methods, there is one area that always seems to slow me down, and that is Integration Request > Mapping Templates. I find that i’m not building these templates frequently enough to memorize the variables, but frequently enough that I find my time being bottle-necked here. This will be my attempt to build a cheat sheet of my most commonly used mapping variables to hopefully speed up this process.

Requests to DynamoDB via Lambda

A common scenario is sending a request to Lambda, which is then making some sort of action on DynamoDB and returning the result. One of the quirks of DynamoDB is that the datatype of the content you are sending to the database is defined with it’s own special syntax, and not contextually, using attribute values.

DynamoDB Attribute Values

unless you want it to use context, in which case you use the document client

DynamoDB Document Client

However the point of this is just to explain, that in many of the examples below we will be submitting all of our values as strings, even if it is a number or boolean, and the datatype would then later be defined in the Lambda function using attribute values. Whether you submit your data as a string or not will depend on the Lambda function on the other end. Let us assume for consistency in this article, that we will be defining all data types in our Lambda function, unless otherwise explicitly stated.

References

API Gateway Docs: Mapping template and access logging reference

Authorizer

authorization in header

One of the most common scenarios in API Gateway is authorizing an incoming request inside of Method Request, to check a Cognito ID Token against one of our user pools. Then after the request is authorized we need to extract that user id (sub) because we will be using it as a sort key (primary or secondary) in a DynamoDB table. In this way we can ensure that a user can only pull their own data from a table.

Mapping_Template
#set($inputRoot = $input.path('$'))
{
  "user_id" : "$context.authorizer.claims.sub"
}

The sub portion of this may be different for you, however inside of a Cognito User Pool this is the unique user id.

This would then be referenced in the Lambda function like so:

event.user_id

Here is another example fetching the user id, email and username from a Cognito JWT Token

Mapping_Template
#set($inputRoot = $input.path('$'))
{
  "user_id": "$context.authorizer.claims.sub",
  "user_email": "$context.authorizer.claims.email",
  "username": "$context.authorizer.claims['cognito:username']",
  "content": $input.json('$')
}

Query

Often times we will want to pull the value from a variable query parameter, which is common in a REST API. For example say we have the following route:

https://widget.com/products/{product_id}

Where product_id is a variable that is being dynamically inserted by our application. So in this case we need to pull this value out of the query string.

Mapping_Template
#set($inputRoot = $input.path('$'))
{
  "queryStringParameters":
   {"product_id": "$input.params().path.get('product_id')"}
}

and for example if we wanted to combine that with the user id from the authorizer as shown above we would get this

Mapping_Template
#set($inputRoot = $input.path('$'))
{
  "user_id" : "$context.authorizer.claims.sub",
  "queryStringParameters":
   {"product_id": "$input.params().path.get('product_id')"}
}

And then to extract that inside of a Lambda function you would use the following reference:

event.queryStringParameters.product_id

There is a way to simplify the reference in the mapping template however, so that we use API gateway to extract the variable from the path.

query string parameters input

If you go into Method Execution > URL Query String Parameters there is a GUI for adding the parameters you want to extract. Which would then allow you to simplify Integration Request > Mapping Template

#set($inputRoot = $input.path('$'))
{
  "queryStringParameters":
   {"code": "$input.params('code')",
   "redirect": "$input.params('redirect')"
   }
}

which would then be extracted in Lambda like so

event.queryStringParameters.code

event.queryStringParameters.redirect

Body

Now let’s say we are dumping a bunch of raw data into the body of a request in JSON format like this (postman screencap)

json data

If we want to map that data we would use the following

Mapping_Template
#set($inputRoot = $input.path('$'))
{
  "product_name" : "$inputRoot.product_name",
  "product_asin" : "$inputRoot.product_asin",
  "product_cogs" : "$inputRoot.product_cogs"
}

and combined with an authorizer we would get this

Mapping_Template
#set($inputRoot = $input.path('$'))
{
  "user_id" : "$context.authorizer.claims.sub",
  "product_name" : "$inputRoot.product_name",
  "product_asin" : "$inputRoot.product_asin",
  "product_cogs" : "$inputRoot.product_cogs"
}

These items would then be referenced in Lambda with the following:

event.product_name

event.product_asin

event.product_cogs

Body Passthrough

That process works, but it has some drawbacks. For example if any of those items are missing, or they come in as an unexpected data type (like a number instead of a string) we will throw an error.

What can be even more useful and flexible is something like this

Mapping_Template
{
  "body" : $input.json('$') 
}

Which will then pass the whole request body through to Lambda, and we can deal with any missing keys or type enforcement there. For example by just using that simple template I can do exactly the same thing but I just get an event in Lambda that looks like this:

console.log(event)
{
  user_id: '12345-1234',
  body: {
    profile_id: 12345,
    request: 'product-campaigns',
    queries: '?stateFilter=enabled'
  }
}

Amazon Ad Analytics For Humans

Advertising reports automatically saved and displayed beautifully for powerful insights.

bidbear.io
portfolios page sunburst chart