REST API using AWS SAM + AMPLIFY - Part 4
Tags: aws, tutorial, awssam, serverless
Published: 2023-08-20 06:59:42

# Back-end (Building SAM)
### Now it is time to create our backend using AWS SAM

**Step 1.** Begin by generating a new directory and name it "amplify-sam-backend". 
```javascript
mkdir amplify-sam-backend
```

**Step 2.** Move into the newly created directory and launch it in Visual Studio Code.
```javascript
cd amplify-sam-backend
code .
```

![Template.yml](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ormaxjhjunjmhzdrsyf6.png)

Subsequently, create a file named **template.yml** within the amplify-sam-backend directory for configuration.


**Step 3.** Now we will define cloud formation notations. Since AWS SAM does not have syntax, this section mainly involves copy and pasting.


- The first step is to specify the AWS template format version, while also indicating that it incorporates the Serverless Transform. 

- Following that, we will introduce parameters to allow for dynamic code deployment to GitHub.

```javascript
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"

Parameters:
  GithubRepository:
    Type: String
    Description: GitHub repository URL
  Stage:
    Type: String
    Description: Deployment stage
```

**Step 4.**   We will configure global variables, specifically for our DynamoDB database, and designate the table with the name "sam-table" (this will be the name of our DynamoDB table).
```javascript

Globals:
  Function:
    Environment:
      Variables:
        TABLE_NAME: sam-table

```

**Step 5.** Next, we will focus on creating our resources, starting with the Amplify Application. We will set the front-end repository name in the property field.  

> NOTE: The name of our front end repository is "amplify-sam-app".

> NOTE: If your repository is set to private mode, the following line should be included under the Repository field:
```
AccessToken: "{{resolve:secretsmanager:github-token}}"
```


```javascript
Name: amplify-sam-app
Repository: !Ref GithubRepository
AccessToken: "{{resolve:secretsmanager:github-token}}"
IAMServiceRole: !GetAtt AmplifyRole.Arn

```

For the scope of our project, the repository is publicly accessible; therefore, including an access token is not required.

```javascript
Resources:
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: amplify-sam-app
      Repository: !Ref GithubRepository
      IAMServiceRole: !GetAtt AmplifyRole.Arn
      EnvironmentVariables:
        - Name: ENDPOINT
          Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/"
```

**Step 6.** we will set up our Amplify branches. In our example, the main branch of our frontend application (amplify-sam-app) is labeled as "main." This serves to direct SAM on how to connect our backend with the respective branch.

> NOTE: (Optional) Multiple branches can be configured if desired.

```javascript
AmplifyBranch:
    Type: AWS::Amplify::Branch
    Properties:
      BranchName: main
      AppId: !GetAtt AmplifyApp.AppId
      EnableAutoBuild: true
```

**Step 7.**  Next, we will establish the necessary IAM roles and permissions required for the effective operation of Amplify. This template encapsulates all the roles and permissions that Amplify necessitates for its operation.
```javascript
AmplifyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - amplify.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: Amplify
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: "amplify:*"
                Resource: "*"
```

**Step 8.**  Next, we will build our API Gateway with CORS configuration. For this project we will set our API Gateway to public access.

> NOTE: For adherence to security best practices, it is recommended to configure authorizers on your API Gateway, or alternatively, restrict access exclusively to a Virtual Private Cloud (VPC).

```javascript
 MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage
      Cors:
        AllowMethods: "'*'"
        AllowHeaders: "'*'"
        AllowOrigin: "'*'"

```

**Step 9.**  Now lets build our resources, API Gateway methods and Lambda functions. 
```javascript
 MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: handler.handler
      Runtime: nodejs16.x
      Policies: AmazonDynamoDBFullAccess
      Events:
        PostApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /car
            Method: POST
        PutApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /car
            Method: PUT
        DeleteApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /car
            Method: DELETE
        HealthCheckApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /check
            Method: GET
        InventoryApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /inventory
            Method: GET


```
This SAM template defines multiple Lambda functions that resides in the current directory (handler.js). It uses Node.js 16x as its runtime, has full access to DynamoDB to perform CRUD operations and is triggered by either GET, POST, PUT or DELETE.

> NOTE: As an alternative you can also use Method: ANY for the Path: /car

```javascript
 MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: handler.handler
      Runtime: nodejs16.x
      Policies: AmazonDynamoDBFullAccess
      Events:
        PostPutDeleteApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /car
            Method: ANY
        HealthCheckApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /check
            Method: GET
        InventoryApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /inventory
            Method: GET

```

**Step 11.** We will establish our DynamoDB table along with the corresponding output parameters. For the ProvisionedThroughput settings of our DynamoDB table, we will opt for a 'PAY_PER_REQUEST' pricing model, effectively incurring charges only for the operations we execute. Accordingly, these settings will be configured to 0.
 
```javascript
DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties: 
      TableName: sam-table
      AttributeDefinitions: 
        - AttributeName: id
          AttributeType: S
      KeySchema: 
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput: 
        ReadCapacityUnits: 0
        WriteCapacityUnits: 0
      
      

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  OrderApi:
    Description: "API Gateway endpoint URL for Prod stage for Order function"
    Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/inventory/"
```

---

Here is our full **template.yml** code:
```javascript

AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"

Parameters:
  GithubRepository:
    Type: String
    Description: GitHub repository URL
  Stage:
    Type: String
    Description: Deployment stage

Globals:
  Function:
    Environment:
      Variables:
        TABLE_NAME: sam-table

Resources:
  AmplifyApp:
    Type: AWS::Amplify::App
    Properties:
      Name: amplify-sam-app
      Repository: !Ref GithubRepository
      IAMServiceRole: !GetAtt AmplifyRole.Arn
      EnvironmentVariables:
        - Name: ENDPOINT
          Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/"

  AmplifyBranch:
    Type: AWS::Amplify::Branch
    Properties:
      BranchName: main
      AppId: !GetAtt AmplifyApp.AppId
      EnableAutoBuild: true

  AmplifyRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - amplify.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: Amplify
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: "amplify:*"
                Resource: "*"

  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage
      Cors:
        AllowMethods: "'*'"
        AllowHeaders: "'*'"
        AllowOrigin: "'*'"


  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: .
      Handler: handler.handler
      Runtime: nodejs16.x
      Policies: AmazonDynamoDBFullAccess
      Events:
        PostApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /car
            Method: POST
        PutApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /car
            Method: PUT
        DeleteApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /car
            Method: DELETE
        HealthCheckApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /check
            Method: GET
        InventoryApi:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /inventory
            Method: GET


  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties: 
      TableName: sam-table
      AttributeDefinitions: 
        - AttributeName: id
          AttributeType: S
      KeySchema: 
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput: 
        ReadCapacityUnits: 0
        WriteCapacityUnits: 0
      
      

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  OrderApi:
    Description: "API Gateway endpoint URL for Prod stage for Order function"
    Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/${Stage}/inventory/"
```

---

**Step 12.** Next,  we will generate a handler.js file within our amplify-sam-backend directory. This file is designated to act as our Lambda handler, encompassing its requisite functionalities. Essentially, this is a Node.js script responsible for processing requests from our API Gateway and fetching data from our DynamoDB table via the AWS SDK.

> NOTE: I plan to publish an additional blog post that will provide an in-depth explanation of this handler.

```javascript


const AWS = require('aws-sdk');

const dynamodb = new AWS.DynamoDB.DocumentClient();
const dynamodbTableName = process.env.TABLE_NAME;
const checkPath = '/check';
const carPath = '/car';
const inventoryPath = '/inventory';

exports.handler = async function(event) {
  console.log('Request event: ', event);
  let response;
  switch(true) {
    case event.httpMethod === 'GET' && event.path === checkPath:
      response = buildResponse(200);
      break;
    case event.httpMethod === 'GET' && event.path === carPath:
      response = await getProduct(event.queryStringParameters.id);
      break;
    case event.httpMethod === 'GET' && event.path === inventoryPath:
      response = await getProducts();
      break;
    case event.httpMethod === 'POST' && event.path === carPath:
      response = await saveProduct(JSON.parse(event.body));
      break;
    case event.httpMethod === 'DELETE' && event.path === carPath:
      response = await deleteProduct(JSON.parse(event.body).id);
      break;
    default:
      response = buildResponse(404, '404 Not Found');
  }
  return response;
}

async function getProduct(id) {
  const params = {
    TableName: dynamodbTableName,
    Key: {
      'id': id
    }
  }
  return await dynamodb.get(params).promise().then((response) => {
    return buildResponse(200, response.Item);
  }, (error) => {
    console.error('Get product error: ', error);
  });
}

async function getProducts() {
  const params = {
    TableName: dynamodbTableName
  }
  const allProducts = await scanDynamoRecords(params, []);
  const body = {
    inventory: allProducts
  }
  return buildResponse(200, body);
}

//Recursize function for DynamoDB scan. DynamoDB limit on return on one querey
async function scanDynamoRecords(scanParams, itemArray) {
  try {
    const dynamoData = await dynamodb.scan(scanParams).promise();
    itemArray = itemArray.concat(dynamoData.Items);
    if (dynamoData.LastEvaluatedKey) {
      scanParams.ExclusiveStartkey = dynamoData.LastEvaluatedKey;
      return await scanDynamoRecords(scanParams, itemArray);
    }
    return itemArray;
  } catch(error) {
    console.error('DynamoDB scan error: ', error);
  }
}

async function saveProduct(requestBody) {
  const params = {
    TableName: dynamodbTableName,
    Item: requestBody
  }
  return await dynamodb.put(params).promise().then(() => {
    const body = {
      Operation: 'SAVE',
      Message: 'SUCCESS',
      Item: requestBody
    }
    return buildResponse(200, body);
  }, (error) => {
    console.error('POST error: ', error);
  })
}


async function deleteProduct(id) {
  const params = {
    TableName: dynamodbTableName,
    Key: {
      'id': id
    },
    ReturnValues: 'ALL_OLD'
  }
  return await dynamodb.delete(params).promise().then((response) => {
    const body = {
      Operation: 'DELETE',
      Message: 'SUCCESS',
      Item: response
    }
    return buildResponse(200, body);
  }, (error) => {
    console.error('Delete error: ', error);
  })
}

function buildResponse(statusCode, body) {
  return {
    statusCode: statusCode,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
      'Access-Control-Allow-Methods': 'GET, POST, PATCH, PUT, DELETE, OPTIONS',
      'Access-Control-Allow-Credentials': 'true',
      'Access-Control-Allow-Origin': '*',
      'X-Request-With': '*'
    },
    body: JSON.stringify(body)
  }
}
```

**Step 13.**  Next,  we will initiate the deployment of our assets to the cloud using the guided SAM deploy process. This action will result in the creation of our Serverless Application Model (SAM) application.

To commence deployment, navigate to the amplify-sam-backend directory within your terminal and execute the following command:
```javascript
sam deploy --guided
```

You will be prompted to enter various parameters:
```
Stack Name: amplify-sam-backend
AWS region: us-east-1
Parameter GithubRepository: https://github.com/'your github username'/amplify-sam-app
Confirm changes before deploy [Y/n]: Y
Allow SAM CLI role creation [Y/n]: Y
Save arguments to configuration file [Y/n]: Y
SAM configuration file [samconfig.toml]: samconfig.toml
SAM configuration environment [default]: 
```

The SAM CLI will orchestrate the deployment to the cloud, a process that generally requires approximately 2-3 minutes for completion.

Upon successful deployment, navigate to your AWS Management Console and proceed to **CloudFormation > amplify-sam-backend > Resources** to confirm that the application has been instantiated as expected.

Finally, return to the AWS Management Console and locate AWS Amplify. Identify our application, denoted as "amplify-sam-app," and select it. Once the deployment has successfully concluded, you may click the provided link to verify the application's functionality.

![amplify](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0akrne0ww8gujjdiit2.png)


---

## Next [Part 5: Modifying the Front-end](https://dev.to/ethanlloyd21/amplify-aws-sam-part-4-front-end-react-typescript-553d)