Using API Gateway mapping templates for direct DynamoDB integrations
Integrating API Gateway directly with DynamoDB can significantly enhance speed by reducing overall response time. Leveraging request and response mapping templates for various DynamoDB operations allows us to remove intermediary Lambda functions in the backend.
POST
endpoint.GET /moths
request later, I want to store their names in a dedicated item in the database.1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"newMoth": {
"PK": "MOTH#Cydia pomonella",
"SK": "MOTH#Cydia pomonella",
"MothName": "Cydia pomonella",
"BaseTemperature": "10",
"EmergingGDU1": "104", // heat accumulation when larvae emerge from the eggs - 1st wave
"MothEng": "Codling moth",
// other properties
},
"toMothList": {
"MothName": "Cydia pomonella"
}
}
newMoth
object contains moth data, necessitating a PutItem
operation. Simultaneously, the toMothList
object specifies the moth's name for the UpdateItem
operation, appending it to the MothsMonitored
list in a dedicated singleton item.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"TransactItems": [
{
"Put": {
"TableName": "Moths",
"Item": {
#set($inputRoot = $input.path('$.newMoth'))
#foreach($key in $inputRoot.keySet())
#set($value = $inputRoot.get($key))
"$key": { "S": "$value" }#if($foreach.hasNext()),#end
#end
},
"ConditionExpression": "attribute_not_exists(PK) AND attribute_not_exists(SK)"
}
},
{
"Update": {
"TableName": "Moths",
"Key": {
"PK": { "S": "MOTHS" },
"SK": { "S": "MOTHS" }
},
"UpdateExpression": "SET #attrName = list_append(if_not_exists(#attrName, :emptyList), :val)",
"ExpressionAttributeNames": {
"#attrName": "MothsMonitored"
},
"ExpressionAttributeValues": {
":val": { "L": [{ "S": "$input.path('$.toMothList.MothName')" }] },
":emptyList": { "L": [] }
}
}
}
]
}
PutItem
and UpdateItem
command codes.#set($inputRoot = $input.path('$.newMoth'))
extracts the object from the input's newMoth
property and stores it in the inputRoot
variable. We then go over the keys using #foreach
and wrap their values inside another object with the S
DynamoDB data type.Put
part is that we must add commas after each item in the map, except the last one. API Gateway doesn't support trailing commas!#if($foreach.hasNext()),#end
part is in the code. hasNext
returns true
if there are more object properties left to iterate over, so in this case, we add the comma. When the logic processes the last item in the collection, hasNext
will be false
, and the conditional block's context (the ,
) will not run.Update
part only contains one VTL syntax: "$input.path('$.toMothList.MothName')"
. We access the second input object here and get the value for MothName
. We add the moth name to the end of the dedicated MOTHS
item's MothsMonitored
list.PutItem
and UpdateItem
actions on the designated DynamoDB table.GET /moth?mothName=<MOTHNAME>
endpoint, I've created an integration response mapping template in API Gateway to transform DynamoDB's moth data into the desired client format.GetItem
operation is performed on DynamoDB, necessitating the API Gateway role having a GetItem
permission.1
2
3
4
5
6
7
{
"TableName": "Moths",
"Key": {
"PK": { "S": "MOTH#$input.params('mothName')" },
"SK": { "S": "MOTH#$input.params('mothName')" }
}
}
GetItem
operation with a payload similar to this:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"Item": {
"PK": {
"S": "Value1"
},
"SK": {
"S": "Value2"
},
"Attribute1": {
"S": "Value1"
},
"Attribute2": {
"S": "Value2"
},
// other attributes
},
"ConsumedCapacity": {
// ...
}
}
1
2
3
4
5
{
"Attribute1": "Value1",
"Attribute2": "Value2",
// other attributes
}
Item
or other wrappers. I also remove the S
data type descriptor from the database response. Thirdly, I don't want to return PK
and SK
or any other local or global secondary index attributes to the client. I only need data attributes in the client. Lastly, I'll return a short message if the requested item is not saved to the database.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#set($inputRoot = $input.path('$'))
#if($inputRoot.Item && !$inputRoot.Item.isEmpty())
#set($excludeKeys = ["PK", "SK"])
#set($resultMap = {})
#foreach($key in $inputRoot.Item.keySet())
#if(!$excludeKeys.contains($key))
#set($dummy = $resultMap.put($key, $inputRoot.Item.get($key).S))
#end
#end
{
#foreach($key in $resultMap.keySet())
"$key": "$resultMap.get($key)"
#if($foreach.hasNext()),#end
#end
}
#else
{"messsage": "No item found"}
#end
resultMap
that stores the item's key/value pairs without the S
data type descriptor. The #if(!$excludeKeys.contains($key))
condition checks if the key is not an index attribute, i.e., not PK
or SK
. If it's not, we store the key/value pair in resultMap
. This way, we can remove PK
and SK
, which don't carry data, from the database response object.resultMap
's keys. Then, we apply the same condition technique to add commas everywhere except after the last element.