Lambda, DynamoDB, and API Gateway for your Jamstack App
Setting up our Lambda Function
After creating an AWS account, search for Lambda in the console, and then click the button that says “Create Function.” We’re going to pick “Author from Scratch”, give it a name, I used songs-store-data
, leave the default Node runtime, and click “Create function” again.
It will give you a default template that looks like this:
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
This will take you to a new screen where you can configure some inputs. We’ll leave the default for now, and save the test name as something like restaurantstoretest
:
Your output will look something like below:
Response:
{
"statusCode": 200,
"body": "\"Hello from Lambda!\""
}
Along with the request ID, and some functions logs. You can get more extensive logs using AWS CloudWatch, but that’s out of the scope of this article.
Great! That output means we have the basics of our first lambda working. It’s worth mentioning that you can also write these functions in your editor locally, there is an option on the left side to add from zip, you can add it there. Or you can zip and add it from the command line:
zip functionReadRestaurants.zip readRestaurants.js
And then create the function with the create-function command (you need to replace the 123456789012
numbers here with your own account ID):
aws lambda create-function --function-name my-function \
--zip-file functionReadRestaurants.zip --handler index.handler --runtime nodejs12.x \
--role arn:aws:iam::123456789012:role/lambda-ex
If you check your dashboard, you can see your function, test it, and hook into CloudWatch and other services just the same way! Working with functions in the UI is fine, though you may find that when you’re handling more logic, it becomes more comfortable to be working in your editor, with the workflows and tools you’re used to.
Now that we have a working function, we can start to build out what we need to access our DynamoDB store and also make sure it has the correct permissions.
Permissions and Connection to DynamoDB
First, we need to give this Function the IAM permission we just created. When building and testing you are in the configuration tab, now let’s click on Permissions, and then Edit Basic Settings:
Under “Execution role”, click on “Use an existing role” and search for our IAM permission, which we named readonly
. Save that to continue.
Now let’s write the function that will pull all our data from DynamoDB. In order to retrieve all of the data, we’ll use the scan
property. You can peruse the rest of this documentation to find that there are so many other properties available to us. We could get a single item, post an item, update, it’s excellent because you have all the CRUD options you may want, and more functionality available to you.
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html#scan-property
Let’s go back to where we author the function in the configuration tab. The first thing we need to do at the top of the file is have Node install the AWS SDK. We then need to create a new AWS DynamoDB instance. Then, pass in our region (in my case, us-east-2, but yours might be different). Also pass in the API version, which will be ‘2012-08-10’ (this is the latest one, this will likely not be different).
When we call dynamoDB.scan, we’ll need to pass our table name in. Let’s also log our errors, and pass the data we retrieve to the callback if it’s successful.
const AWS = require('aws-sdk')
const dynamoDB = new AWS.DynamoDB({ region: 'us-east-2', apiVersion: '2012-08-10' })
exports.handler = (event, context, cb) => {
const params = {
TableName: 'Restaurants'
}
dynamoDB.scan(params, (err, data) => {
if (err) {
console.log(err)
cb(err)
} else {
console.log(`Data is ${data}`)
cb(null, data)
}
})
};
Which outputs something like this, you saw this type of format for JSON earlier when we talked about creating the table:
Response:
{
"Items": [
{
"menu": {
"L": [
{
"M": {
"description": {
"S": "Whole shrimp in a translucent wrapper."
},
"item": {
"S": "Shrimp Dumplings"
},
"img": {
"S": "dimsum-shrimp.jpg"
},
"id": {
"S": "22672b93-2c65-4fd9-b151-683f7eb7df4a"
},
"price": {
"N": "6.49"
},
"addOns": {
"SS": [
"mango pudding",
"none",
"steamed sesame ball",
"sweet tofu"
]
}
}
},
{
"M": {
"description": {
"S": "Dumplings filled with pork, crab meat and broth."
},
"item": {
"S": "Soup Dumplings"
},
"img": {
"S": "dimsum-soup.jpg"
},
"id": {
"S": "3a6da02c-2354-4eb8-af07-f9f2d26fa7c2"
},
"price": {
"N": "9.99"
},
"addOns": {
"SS": [
"mango pudding",
"none",
"steamed sesame ball",
"sweet tofu"
]
}
}
},
…
}
You can see that we’re now outputting all the values, but we’re also outputting the types as well. They’re denoted by the “S” and “N” and “SS”, which is not exactly what we would like because it will make it incredibly difficult to access when we’re writing our applications. The JSON isn’t that “clean”. Thankfully, now DynamoDB provides a method called “unmarshalled”:
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/Converter.html#unmarshall-property
This is awesome! It means we don’t have to map over all the properties and convert them, which is a big pain (if you only have a couple of properties, a good ol’ .map()
will do just fine). The usage is like this:
const AWS = require('aws-sdk')
const dynamoDB = new AWS.DynamoDB({ region: 'us-east-2', apiVersion: '2012-08-10' })
exports.handler = (event, context, cb) => {
const params = {
TableName: 'Restaurants'
}
dynamoDB.scan(params, (err, data) => {
if (err) {
console.log(err)
cb(err)
}
else {
const unmarshalledData = data.Items.map(el => {
return AWS.DynamoDB.Converter.unmarshall(el)
})
const response = {
"statusCode": 200,
"body": JSON.stringify(unmarshalledData),
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": true
}
};
console.log(response)
cb(null, response)
}
})
};
And now our output is:
[
{
"name": "All that and Dim Sum",
"id": "1f9500d2-fd27-422e-acc6-48198d0de5c2",
"rating": 4.9,
"deliveryTime": 35,
"freeDelivery": true,
"menu": [
{
"description": "Whole shrimp in a translucent wrapper.",
"item": "Shrimp Dumplings",
"img": "dimsum-shrimp.jpg",
"id": "22672b93-2c65-4fd9-b151-683f7eb7df4a",
"price": 6.49,
"addOns": [
"mango pudding",
"none",
"steamed sesame ball",
"sweet tofu"
]
},
…
]
So much cleaner!
Small note: you may notice that JSON.stringified (is that how you past-tense that?) the unmarshalledData. This is what API Gateway will expect. Don’t lose time on that like I did 😅