Deploy AWS resources using serverless framework
Tags: serverless, aws, iac
Published: 2022-08-25 20:43:35

In this article I will show you how you can deploy your AWS resources using the serverless framework and IaC (Infrastructure as Code).

I will use the AWS services [AWS Lambda](https://aws.amazon.com/lambda/) and [Amazon DynamoDB](https://aws.amazon.com/dynamodb/). I will also use the [Serverless framework](https://www.serverless.com) and [NodeJS](https://nodejs.org/en/).

I will show you how you can deploy differnt Lambdas using serverless and also how you create a DynamoDB table using serverless.

The project will be a simple ToDo app where we will create the Lambdas for adding and fetching an item.

Note that this article will focus on the **serverless.yml** file and the code to the Lambdas will not be the focus hence not explained. If you have any comments on those or questions feel free to ask these in the comments.

### Prerequisites:

1. AWS account
2. AWS CLI installed - [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html) is a guide of how to give your AWS CLI access to your AWS account
3. Serverless framework installed - guide [here](https://www.serverless.com/framework/docs/getting-started)

### Initialize project
Open terminal and type:

```
serverless
```

You will now be prompted with some options, choose:

```
AWS - Node.js - HTTP API 
```

Add a name to your project or click enter to select the suggested name.

For the next question, I have chosen **n**:

```
Do you want to login/register to Serverless Dashboard? n
```

For the next question, I have chosen **n**:

```
Do you want to deploy now? n
```

Open the newly created project in an editor, I will be using Visual Studio Code.

### Open project
Let's go through what we currently have in our project structure:

- .gitignore
- handler.js - our handler with a sample module created
- README.md
- serverless.yml - this is the core file that we will build to deploy our services to AWS

### serverless.yml
A service is configured via a serverless.yml file where you define your functions, the events that trigger them, and the AWS resources to deploy.

Our current file looks like this:

```yaml
service: aws-node-http-api-project
frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs14.x

functions:
  hello:
    handler: handler.hello
    events:
      - httpApi:
          path: /
          method: get
```

**service** = what we're calling our service
**frameworkVersion** = the version of the serverless framework that is used
**provider** = the cloud provider, runtime for our functions, 
**functions** = current functions, we currently have one in our handler.js
**functions.hello** = this is where we have added our functions properties such as the handler and the events which triggers the event

### Commands that we will be using
These are the commands that we will be using:

- serverless deploy - Deploy changes
- serverless deploy -f <function> - Deploy a specific function
- serverless info - View deployed endpoints and resources
- serverless invoke - Invoke deployed functions
- serverless --help - Discover more commands

> A tip: you can use the command `sls` instead of typing `serverless`. So for example `sls deploy`

### Let's start building our add item function
Start by adding a **src** folder in your project and remove the current **handler.js** file.

Inside the src folder add a file called **add.js** and add the following code:

```javascript
const { v4 } = require('uuid')
const AWS = require('aws-sdk')

const add = async (event) => {

  const dynamoDB = new AWS.DynamoDB.DocumentClient();

  const { todo } = JSON.parse(event.body);
  const createdAt = new Date().toDateString()
  const id = v4();

  const newTodo = {
    id,
    todo,
    createdAt,
    completed: false
  };

  await dynamoDB.put({
    TableName: 'TodoTable',
    Item: newTodo
  }).promise();

  return {
    statusCode: 200,
    body: JSON.stringify(newTodo)
  };
};

module.exports = {
  handler: add
}
```

As you can see we have code added to add the item to our DynamoDB database but that is not yet created, but it's okey we'll create it after this step.

Now go to your serverless.yml file and we'll add this new function that we just created. 

First of all we want to add the region in the **provider** section. Add the region you want to create your services in, mine is in `region: eu-north-1`. Note that the indentation is important in the .yml file and the region has to be in the same indentation as the runtime.

Now go down to the **functions** section and remove the **hello function** and start by adding the following indented one tab below the **functions**:

```javascript
add: // this is the name of our function
    handler: src/add.handler // our handler which is located in the src folder and is called add.handler
   events: // how we call this function
       - httpApi: // - this dash in .yml means an array which we will use to define multiple parameters
          path: / // no specific path will be used but it could be for example /addItem
          method: post // this is a post
```

We're now ready to deploy our first Lambda function through IaC. Open the terminal and type:

```
sls deploy
```

The deploy can take a minute or two but when it's done you will can an output like the following:

```
Deploying aws-node-http-api-project to stage dev (eu-north-1)

✔ Service deployed to stack aws-node-http-api-project-dev (104s)

endpoint: POST - https://...execute-api.eu-north-1.amazonaws.com/
functions:
  add: aws-node-http-api-project-dev-add (1.9 kB)

```

The endpoint is how you reach this Lambda and since we didn't add any specific path then it's enough for this function to do the following to add a new item:

```
method: POST
endpoint: https://...execute-api.eu-north-1.amazonaws.com/
body: {
           "todo": "My first item"
      }

```

If you go to your AWS console you should now be able to see your newly created function in the region you created it in.

### Let's create our database
Go to your **serverless.yml** file and add the following below the entire functions section:

```yaml
resources:
  Resources:
      TodoTable:
        Type: AWS::DynamoDB::Table
        Properties:
          TableName: TodoTable
          BillingMode: PAY_PER_REQUEST
          AttributeDefinitions:
            - AttributeName: id
              AttributeType: S
          KeySchema:
            - AttributeName: id
              KeyType: HASH
```

What we have done here is the following:

1. Created a new resource called TodoTable
2. The type is AWS::DynamoDB::Table
3. We have added a couple of properties such as TableName and BillingMode
4. AttributeDefinitions represents an attribute for describing the key schema for the table and indexes
5. KeySchema specifies the attributes that make up the primary key for a table or an index

You can now deploy your infrastructure by typing:

```
sls deploy
```

If you run your add function you will notice that it will not write anything to your table and this is because our functions cannot access the DynamoDB table we've just created and we need to provide an IAM role for this. Go to provider and under **region**, add the following:

```yaml
 iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:*
      Resource:
        - <your DynamoDB's ARN that can be accessed from the console>
```

In this role we're saying that All our functions is allowed to do any actions in our DynamoDB table. Please not that for testing purposes I have added the action `dynamodb:*`, do not use this when going live with a table.

If you now deploy again and run the POST function you will see that you have an item in your DynamoDB table.

### Let's fetch a specific item from our table
In your **src** folder create a new file called fetch.js and add the following code:

```javascript
const AWS = require('aws-sdk')

const fetch = async (event) => {

    const dynamoDB = new AWS.DynamoDB.DocumentClient();
    const { id } = event.pathParameters;
    let todo;

    try {
        const result = await dynamoDB.get({
            TableName: 'TodoTable',
            Key: { id }
        }).promise();
        todo = result.Item;
    } catch (error) {
        console.log(error);
    };

    return {
        statusCode: 200,
        body: JSON.stringify(todo)
    };
};

module.exports = {
    handler: fetch
}
```

Now go to your serverless.yml file and add the following under the add function:

```yaml
fetch:
    handler: src/fetch.handler
    events:
      - httpApi:
          path: /todo/{id}
          method: get

```

As you can see it follows the same structure as the add but with the main difference is that here we want to be able to pass in an id so that we can fetch for that in the database.

Run a deploy and you will get the following output:

```
Deploying aws-node-http-api-project to stage dev (eu-north-1)

✔ Service deployed to stack aws-node-http-api-project-dev (50s)

endpoints:
  POST - https://...execute-api.eu-north-1.amazonaws.com/
  GET - https://...execute-api.eu-north-1.amazonaws.com/todo/{id}
functions:
  add: aws-node-http-api-project-dev-add (2.3 kB)
  fetch: aws-node-http-api-project-dev-fetch (2.3 kB)
```

As you can see we have two endpoints and two functions. Now you can do the following to access a specific item in your table:
```
method: GET
endpoint: https://...execute-api.eu-north-1.amazonaws.com/todo/{id}
```

### Deploy
A final word about deploys, right now we have made changes to our serverless.yml file hence we need to make an `sls deploy` which takes 1-2 minutes but let's say that you only make changes in your **add.js** file and want to deploy those changes then it's enough with the following which takes a couple of seconds:

```
sls deploy -f add
```

Above will only deploy that function.

### Summary
IaC is a good way of having a code-based approach which makes it easier to get more done in less time. There is a lot of different providers for using IaC and this was one example.

Any comments, questions or discussions are always welcome!