Getting Started with Serverless AWS (3/3) - Micro Services using Lambda, DynamoDB & API Gateway
Tags: webdev, aws, devops, tutorial
Published: 2020-07-06 05:21:51

1. [Deploy a Static Website to S3](https://dev.to/vincenius/getting-started-with-serverless-aws-1-3-deploying-a-static-website-to-s3-1mip)
2. [Distribute your S3 Bucket with Cloudfront and add a Domain](https://dev.to/vincenius/getting-started-with-serverless-aws-2-3-distribute-an-s3-bucket-with-cloudfront-and-add-a-domain-jgm)
3. [Micro Services using Lambda, DynamoDB & API Gateway](https://dev.to/vincenius/getting-started-with-serverless-aws-3-3-micro-services-using-lambda-dynamodb-api-gateway-32ae)

____

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](https://wweb.dev/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](https://wweb.dev/blog/aws3/2-dynamodb-create.jpg)![settings of new dynamodb](https://wweb.dev/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](https://wweb.dev/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](https://wweb.dev/blog/aws3/5-services-iam.jpg)

Go to roles on the left menu and click `Create role`.

![IAM create new role](https://wweb.dev/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](https://wweb.dev/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](https://wweb.dev/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](https://wweb.dev/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](https://wweb.dev/blog/aws3/10-lambda-service.jpg)![Lambda create](https://wweb.dev/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](https://wweb.dev/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:

```javascript
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](https://wweb.dev/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.

```json
{
  "title": "my awesome post"
}
```

![Lambda create test](https://wweb.dev/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.

```javascript
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](https://github.com/awslabs/aws-shell) setup. If you haven't done this yet, you can head back to [Part 1](https://wweb.dev/blog/aws-getting-started-deploy-static-website-s3) 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](https://wweb.dev/blog/aws3/14-api-gateway-service.jpg)![Create API Gateway](https://wweb.dev/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](https://wweb.dev/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](https://wweb.dev/blog/aws3/17-api-gateway-method.jpg)![Create API Gateway Method Details](https://wweb.dev/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](https://wweb.dev/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](https://wweb.dev/blog/aws3/20-api-gateway-deploy.jpg)![Deploy API Gateway](https://wweb.dev/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](https://wweb.dev/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](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-set-up-lambda-proxy-integration-on-proxy-resource)). This has its own [advantages and disadvantages.](https://stackoverflow.com/a/41428226/10785177)

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

```javascript
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
    };
  }
};
```

```javascript
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
    };
  }
};
```

```javascript
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
    };
  }
};
```

```javascript
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](https://wweb.dev/blog/aws3/24-api-gateway.jpg)

Create an endpoint for getting all posts in the table.

![API Gateway get all endpoint](https://wweb.dev/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](https://wweb.dev/blog/aws3/26-api-gateway-resource.jpg)![API Gateway create resource details](https://wweb.dev/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](https://wweb.dev/blog/aws3/28-create-get.jpg)

When you're done creating the endpoints, your API should look like this:

![Final API Gateway](https://wweb.dev/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](https://wweb.dev/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:

Get all
```
curl --location --request GET 'https://your-invoke-url'
```

Get by ID
```
curl --location --request GET 'https://your-invoke-url/some-id'
```

Update by ID
```
curl --location --request PUT 'https://your-invoke-url/some-id'  --header 'Content-Type: text/plain'  --data-raw '{ "title": "updated title from API" }'
```

Delete by ID
```
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.