- Deploying a Static Website to S3
- Distribute an S3 Bucket with CloudFront and add a Domain
- Micro Services using Lambda, DynamoDB & API Gateway
In this part, we're creating a DynamoDB. Then we create a Lambda for creating, reading, updating, and deleting entries in that database. And last we create an API Gateway to trigger those Lambdas.
DynamoDB
First, open the service DynamoDB.
DynamoDB is a NoSQL database. NoSQL databases are purpose-built for specific data models and have flexible schemas.
![dynamodb service](/blog/aws3/1-dynamodb-service.jpg)
Then click Create Table
and specify the name and the primary key of your table. I will call mine posts
with the primary key id
. This primary key has to be unique for all your items in the table.
You could also specify a sort key to provide more querying flexibility. I will skip this for simplicity.
![dynamodb create new](/blog/aws3/2-dynamodb-create.jpg)
![settings of new dynamodb](/blog/aws3/3-dynamodb-create-settings.jpg)
Now you should see the overview of your freshly created table. You can go to Items to be able to see all items in your table. You can also manually add them here.
![items of dynamodb](/blog/aws3/4-dynamodb-items.jpg)
To access this table we will create a Lambda function. AWS Lambda is a computing service that lets you run code without managing servers. AWS Lambda executes your code only when needed and scales automatically.
Adding a Lambda IAM role
For a lambda function to be able to access a DynamoDB it needs the permissions to do so. Thus we need to head to the IAM service and create a new role.
![IAM service](/blog/aws3/5-services-iam.jpg)
Go to roles on the left menu and click Create role
.
![IAM create new role](/blog/aws3/6-iam-create.jpg)
Now you have to select the service, which will use the role. Click on Lambda
and then Next: Permissions
.
![IAM create lambda permissions](/blog/aws3/7-iam-create-lambda.jpg)
In this step, we need to specify the permissions our lambda will get. We want our lambda to be able to access the DynamoDB. So type dynamo
and select AmazonDynamoDBFullAccess
.
![IAM dynamodb access](/blog/aws3/8-iam-dynamodb.jpg)
Skip the step of setting tags. On the last step choose a name for the new role. I'll call it lambda_dynamo
. Then click Create role
.
![IAM create role](/blog/aws3/9-iam-create.jpg)
Creating the Lambda function
Now we're ready to create the lambda function. Go to the Lambda service and click on Create Function
.
![Lambda service](/blog/aws3/10-lambda-service.jpg)
![Lambda create](/blog/aws3/11-lambda-create.jpg)
I create the lambda for creating DynamoDB entries first. I'll call the function createPost
. Then on the bottom click Choose or create an execution role
and select the IAM role we just created. Afterward, click Create function
.
![Lambda creation details](/blog/aws3/12-lambda-details.jpg)
In the function view, scroll down to get to the editor.
The code for adding an item to the DynamoDB looks like this:
const AWS = require("aws-sdk");
const crypto = require("crypto");
// Initialising DynamoDB SDK
const documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async body => {
const params = {
TableName: "posts", // name of your DynamoDB table
Item: { // Creating an Item with a unique id, the created date and the passed title
id: crypto.randomBytes(16).toString("hex"),
created_at: new Date().toISOString(),
title: body.title,
}
};
try {
// https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.03.html#GettingStarted.NodeJs.03.01
const data = await documentClient.put(params).promise();
return {
statusCode: 201
};
} catch (e) {
return {
statusCode: 500,
body: JSON.stringify(e)
};
}
};
Afterward, click Test
, to check if your function is working.
![Lambda code](/blog/aws3/13-lambda-code.jpg)
Because we haven't specified any tests yet, a modal will open.
On this test modal, we need to specify a test name and the body, which is passed to the function. In my case it is only expecting a title
. Thus, I'll enter following JSON.
{
"title": "my awesome post"
}
![Lambda create test](/blog/aws3/13-5-lambda-create-test.jpg)
Now you can click Test
again, to execute the test call.
If everything worked correctly, you should see a success message with the status code 201
.
You can also go back to your DynamoDB to verify that the Item was created successfully.
Deploy a Lambda from your local machinge
Deploying from your machine makes sense if you want to extend this function in the future. This section is optional and you can skip it if you're not interested in deploying from your machine.
So open up an empty workspace in your preferred code editor. Then create a new file called index.js
. In this file add the following content.
const AWS = require("aws-sdk");
const { v4: uuidv4 } = require('uuid');
// Initialising the DynamoDB SDK
const documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async body => {
const params = {
TableName: "posts", // name of your DynamoDB table
Item: { // create item with a unique id, the created date and the passed title
id: uuidv4(),
created_at: new Date().toISOString(),
title: body.title,
}
};
try {
// https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.03.html
const data = await documentClient.put(params).promise();
return {
statusCode: 201
};
} catch (e) {
return {
statusCode: 500,
body: JSON.stringify(e)
};
}
};
This is almost the same function we used before for our Lambda. The only difference is, that I used the package uuid
for generating a unique id.
This is because I also want to go through the process of adding node modules to our Lambda. So open up the terminal and navigate to your workspace. Then type:
npm i --save uuid
To be able to deploy our function to AWS, we need to zip it first.
zip -r function.zip .
This will create a new .zip file in our project root with all the content of the workspace.
Now our function is ready to be deployed. For that, you need to have the aws-shell setup. If you haven't done this yet, you can head back to Part 1 of this series and search for aws-shell
.
For deploying to AWS enter following command:
aws lambda update-function-code --function-name yourFunctionName --zip-file fileb://function.zip
You have to replace yourFunctionName
with the name of your Lambda function.
If you didn't specify a region on aws configure
you need to append the region to your command with --region yourRegion
. You can find your region on the top right of the AWS management console.
Now you can go back to the AWS management console and check if the code was updated. Also, feel free to run the test again to see if everything is still working.
Create an API for your Lambda
To be able to call your Lambda, go to the service API Gateway
. Then scroll down and click on Build
of the card REST API
.
![API Gateway Service](/blog/aws3/14-api-gateway-service.jpg)
![Create API Gateway](/blog/aws3/15-api-gateway-create.jpg)
Then select New API
and enter a name for yours. To keep it simple, use the type Regional
. Then hit Create API
.
![Create API Gateway Details](/blog/aws3/16-api-gateway-create.jpg)
Then click on Actions
and Create Method
. Now choose your Lambda function, which is in my case postLambda
, and click Save
.
![Create API Gateway Method](/blog/aws3/17-api-gateway-method.jpg)
![Create API Gateway Method Details](/blog/aws3/18-api-gateway-method-create.jpg)
You should see a modal, which is asking you to give permissions for your API Gateway. Click OK
to allow your API Gateway to access your Lambda function.
![Create API allow lambda access](/blog/aws3/19-api-gateway-modal.jpg)
To be able to call our API, we need to deploy it first. So go into Actions
and click Deploy API
. Then create a new Stage. I'll call mine production
, but this can be anything.
![Deploy API Gateway](/blog/aws3/20-api-gateway-deploy.jpg)
![Deploy API Gateway](/blog/aws3/21-api-gateway-deploy.jpg)
On top of the page, you will see the Invoke URL
. If you click on it you will get a Missing Authentication Token
error. Don't get confused by this. This is because we haven't specified a GET endpoint yet. You should be able to call the POST endpoint of this URL. On Postman, it would look like this.
![Invoke Post endpoint postman](/blog/aws3/23-postman.jpg)
Or you can also call this endpoint from your terminal with curl
curl --location --request POST 'https://your-invoke-url' --header 'Content-Type: text/plain' --data-raw '{ "title": "post from API" }'
As the next step, create the Lambdas for reading, updating and deleting entries in the DynamoDB.
It is also possible to do this all in one lambda function. (See here). This has its own advantages and disadvantages.
But I will go for the approach of having separate lambdas for separate endpoints.
Read, Update and Delete Lambdas
Head back to the Lambda service and create the other four Lambdas with the same steps as described earlier in this post. Don't forget to use the lambda-dynamo
role.
Use the following code for the Lambdas
const AWS = require("aws-sdk");
const documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async () => {
const params = {
TableName: "posts" // name of your DynamoDB table
};
try {
// use the scan method to get all items in the table
const data = await documentClient.scan(params).promise();
const response = {
statusCode: 200,
body: JSON.stringify(data.Items)
};
return response;
} catch (e) {
return {
statusCode: 500
};
}
};
const AWS = require("aws-sdk");
const documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async event => {
const {
pathParameters: { id }
} = event; // Extracting the id from the request path
const params = {
TableName: "posts", // name of your DynamoDB table
Key: { id } // key of the item you want to find.
};
try {
// use the get method to fetch an indvidual item
const data = await documentClient.get(params).promise();
const response = {
statusCode: 200,
body: JSON.stringify(data.Item)
};
return response;
} catch (e) {
return {
statusCode: 500
};
}
};
const AWS = require("aws-sdk");
const documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async event => {
const {
pathParameters: { id }
} = event; // get id from path
const { title } = JSON.parse(event.body);
const params = {
TableName: "posts",
Item: {
id: id,
title: title
}
};
try {
// use put method to find and update
const data = await documentClient.put(params).promise();
const response = {
statusCode: 200
};
return response;
} catch (e) {
return {
statusCode: 500
};
}
};
const AWS = require("aws-sdk");
const documentClient = new AWS.DynamoDB.DocumentClient();
exports.handler = async event => {
const {
pathParameters: { id }
} = event; // get id from path
const params = {
TableName: "posts",
Key: { id }
};
try {
// use delete method the remove item
const data = await documentClient.delete(params).promise();
const response = {
statusCode: 200
};
return response;
} catch (e) {
return {
statusCode: 500
};
}
};
Updating the API Gateway
After you've created all those lambdas, go to the API Gateway service. Open your before created API.
![API Gateway](/blog/aws3/24-api-gateway.jpg)
Create an endpoint for getting all posts in the table.
![API Gateway get all endpoint](/blog/aws3/25-api-gateway-get-all.jpg)
For getting, updating, or deleting a single item, we're getting the id of the element from the URI. To provide that id, create a new resource.
![API Gateway create resource](/blog/aws3/26-api-gateway-resource.jpg)
![API Gateway create resource details](/blog/aws3/27-api-gateway-resource-create.jpg)
Afterward, add a GET, a PUT, and a DELETE method to this sub-path. For those, you need to enable Use Lambda Proxy integration
to be able to access the id from the URL.
![API Gateway create get by id endpoint](/blog/aws3/28-create-get.jpg)
When you're done creating the endpoints, your API should look like this:
![Final API Gateway](/blog/aws3/29-api-gateway-done.jpg)
As a last step deploy your API again by clicking Actions
and then Deploy API
. Select the same stage to overwrite it with the new state.
![Deploy API Gateway](/blog/aws3/30-api-gateway-deploy.jpg)
Congratulations. Your REST API is now up and running. Here are the calls for your shell to call those endpoints:
curl --location --request GET 'https://your-invoke-url'
curl --location --request GET 'https://your-invoke-url/some-id'
curl --location --request PUT 'https://your-invoke-url/some-id' --header 'Content-Type: text/plain' --data-raw '{ "title": "updated title from API" }'
curl --location --request DELETE 'https://your-invoke-url/some-id'
Thanks for reading this tutorial.
If you had problems following along at some point, please let me know.