Deploy & Run your Node.js Framework App on AWS Lambda with AWS Lambda Web Adapter
Tags: node, hapijs, serverless, lambda
Published: 2023-10-24 09:54:49

In this article, we'll explore the process of deploying and running a serverless application on AWS Lambda built with Node.js framework such as Hapi.js and NoSQL database like Dynamodb.

> In this article, we will use the application that we previously built from [this article](https://dev.to/aws-builders/unlocking-the-power-of-nosql-building-a-todo-api-with-hapijs-and-dynamodb-1f7n).

We will use [AWS Lambda Web Adapter](https://github.com/awslabs/aws-lambda-web-adapter) to run our Node.js Application on AWS Lambda.

> AWS Lambda Web Adapter allows developers to build web apps (http api) with familiar frameworks (e.g. Express.js, Next.js, Flask, SpringBoot, ASP.NET and Laravel, anything speaks HTTP 1.1/1.0) and run it on AWS Lambda.

## Follow me for more
{% embed https://linktr.ee/fullstacksaiyan %}

## AWS Lambda

AWS Lambda is a serverless computing service provided by Amazon Web Services (AWS). It allows you to run code without having to provision or manage servers. With Lambda, you can focus on writing your application code and let AWS handle the infrastructure management, scaling, and server maintenance.

## Amazon DynamoDB

Amazon DynamoDB is a fully managed NoSQL database service provided by Amazon Web Services (AWS). It is designed to provide fast and scalable performance for applications that require flexible and reliable data storage. DynamoDB is particularly well-suited for web and mobile applications, gaming backends, content management systems, and other use cases that need low-latency, highly available, and scalable databases.

## AWS Serverless Application Model

AWS SAM (Serverless Application Model) is an open-source framework provided by Amazon Web Services (AWS) for building serverless applications. It extends AWS CloudFormation, which is AWS's infrastructure as code service, to provide a simplified way of defining the resources and configurations needed to deploy serverless applications on AWS.

## Node.js

Node.js is an open-source, server-side runtime environment that allows developers to build and run server-side applications using JavaScript. It is built on the V8 JavaScript engine, which is the same engine used in Google Chrome, and it enables the execution of JavaScript code on the server, rather than just in web browsers.

## Node.js Framework

Node.js has a rich ecosystem of frameworks and libraries to simplify web application development. Some popular Node.js frameworks and libraries include:

1. Express.js
2. Koa.js
3. Sails.js
4. Hapi.js
5. Nest.js

> In this article, we will deploy and run [our application](https://dev.to/aws-builders/unlocking-the-power-of-nosql-building-a-todo-api-with-hapijs-and-dynamodb-1f7n) built with Hapi.js and Amazon DynamoDB.

## Prerequisites Tools

- [Install AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
- [Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html)
- [Install AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)

## Table of Contents

- [Project Folder Structure](#step1)
- [Dockerfile](#step2)
- [AWS SAM Template.yaml](#step3)
- [AWS SAM Deploy](#step4)
- [Test](#step5)
- [Cleanup](#step6)
- [Conclusions](#step7)

## Project Folder Structure <a name="step1"></a>

Our project folder structure will looks like this

```sh
.
|-- app
|   |-- src
|   `-- Dockerfile
`-- template.yaml
```

- `src` is a folder that will houses our Node.js application.
- `Dockerfile` is a text file used to define a set of instructions for building our Docker container image.
- `template.yaml` is an AWS SAM template, providing a streamlined layer on top of CloudFormation, simplifying the definition and deployment of our serverless applications.

## Dockerfile <a name="step2"></a>

This is how our Dockerfile looks like

```yaml
FROM public.ecr.aws/docker/library/node:16.13.2-stretch-slim
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.7.1 /lambda-adapter /opt/extensions/lambda-adapter
EXPOSE 8080
WORKDIR "/var/task"
ADD src/package.json /var/task/package.json
ADD src/package-lock.json /var/task/package-lock.json
RUN npm install --omit=dev
ADD src/ /var/task
CMD ["node", "index.js"]
```

This is a Dockerfile that defines the build instructions for a Docker image. The image is based on the `node:16.13.2-stretch-slim` image from Docker Hub, which is a lightweight version of the Node.js runtime environment.

To use Lambda Web Adapter with docker images, add one line to copy Lambda Web Adapter binary to /opt/extensions inside your container.

The Dockerfile starts by copying the `lambda-adapter` binary from another Docker image (`public.ecr.aws/awsguru/aws-lambda-adapter:0.7.1`) to the `/opt/extensions/lambda-adapter` directory in the new image.

The `EXPOSE` instruction is used to indicate that the container will listen on port 8080. This does not actually publish the port, but it is a useful hint for anyone who wants to run the container.

The `WORKDIR` instruction sets the working directory for subsequent instructions to `/var/task`. This is the directory where the Lambda function code will be located.

The `ADD` instructions copy the `package.json`, `package-lock.json`, and source code files from the `src` directory to the `/var/task` directory in the image. The `npm install` command is then run to install the dependencies specified in `package.json`. The `--omit=dev` flag is used to exclude development dependencies from the installation, which can help reduce the size of the final image.

Finally, the `CMD` instruction specifies the command that should be run when a container is started from the image. In this case, it runs the `node` command with `index.js` as the argument, which is the entry point for the Lambda function.

## SAM Template <a name="step3"></a>

AWS SAM (Serverless Application Model) template is a JSON or YAML configuration file used to define the AWS resources and configurations required to deploy a serverless application on Amazon Web Services (AWS).

This is how our complete AWS SAM template will look like.

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Todo Application

Resources:
  TodoFunction:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      MemorySize: 1024
      Timeout: 30
      Environment:
        Variables:
          RUST_LOG: info
          TABLE_NAME: !Ref TodoTable
      Events:
        Root:
          Type: HttpApi
          Properties:
            Path: /
            Method: ANY
        Petstore:
          Type: HttpApi
          Properties:
            Path: /{proxy+}
            Method: ANY
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref TodoTable
    Metadata:
      DockerTag: v1
      DockerContext: ./app
      Dockerfile: Dockerfile

  TodoTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: id
        Type: String

Outputs:
  TodoApi:
    Description: "API Gateway endpoint URL for Prod stage for Todo function"
    Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/"
```

First, we defined our Lambda Function resource like this in our template.

```yaml
TodoFunction:
  Type: AWS::Serverless::Function
  Properties:
    PackageType: Image
    MemorySize: 1024
    Timeout: 30
    Environment:
      Variables:
        RUST_LOG: info
        TABLE_NAME: !Ref TodoTable
    Events:
      Root:
        Type: HttpApi
        Properties:
          Path: /
          Method: ANY
      Petstore:
        Type: HttpApi
        Properties:
          Path: /{proxy+}
          Method: ANY
    Policies:
      - DynamoDBCrudPolicy:
          TableName: !Ref TodoTable
  Metadata:
    DockerTag: v1
    DockerContext: ./app
    Dockerfile: Dockerfile
```

The above template part defines an AWS Lambda function named `TodoFunction` that is used to handle HTTP requests for a RESTful API.

The `Type` property specifies that this resource is an AWS::Serverless::Function, which is a type of AWS Lambda function that can be defined using the SAM template format.

The `Properties` section contains various configuration options for the function. The `PackageType` property is set to `Image`, which means that the function will be packaged as a Docker image. The `MemorySize` property is set to 1024 MB, which is the amount of memory that will be allocated to the function when it runs. The `Timeout` property is set to 30 seconds, which is the maximum amount of time that the function can run before it is terminated.

The `Environment` section contains environment variables that will be available to the function at runtime. In this case, there are two variables defined: `RUST_LOG` and `TABLE_NAME`. The `RUST_LOG` variable is set to `info`, which is a logging level for the Rust programming language. The `TABLE_NAME` variable is set to the name of a DynamoDB table that the function will interact with. The `!Ref` function is used to reference the `TodoTable` resource defined elsewhere in the template.

The `Events` section defines the events that will trigger the function. In this case, there are two events defined: `Root` and `Petstore`. Both events are of type `HttpApi`, which means that they will be triggered by HTTP requests. The `Path` property specifies the URL path that will trigger the event, and the `Method` property specifies the HTTP method that will trigger the event.

The `Policies` section defines the IAM policies that are attached to the function. In this case, there is one policy defined: `DynamoDBCrudPolicy`. This policy grants the function permissions to perform CRUD (Create, Read, Update, Delete) operations on the DynamoDB table specified in the `TableName` property. The `!Ref` function is used to reference the `TodoTable` resource defined elsewhere in the template.

The `Metadata` section contains metadata about the function. In this case, it specifies the Docker tag, Docker context, and Dockerfile that will be used to build the Docker image for the function.

Then we create our Amazon DynamoDB table

```yaml
TodoTable:
  Type: AWS::Serverless::SimpleTable
  Properties:
    PrimaryKey:
      Name: id
      Type: String
```

The above template part defines an AWS DynamoDB table named `TodoTable` that will be used to store data for a RESTful API.

The `Type` property specifies that this resource is an AWS::Serverless::SimpleTable, which is a type of DynamoDB table that can be defined using the SAM template format.

The `Properties` section contains various configuration options for the table. The `PrimaryKey` property specifies the primary key for the table, which consists of a single attribute named `id` of type `String`. This means that the `id` attribute will be used as the partition key for the table.

Last, we defined our function's API Gateway endpoint URL output

```yaml
Outputs:
  NodejsApi:
    Description: "API Gateway endpoint URL for Prod stage for Todo function"
    Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.${AWS::URLSuffix}/"
```

It defines an output named `TodoApi` that provides the URL for the API Gateway endpoint for the `TodoFunction` resource.

The `Outputs` section of the template is used to define values that can be exported from the stack. These values can be used by other stacks or by external applications to interact with the resources in the stack.

The `TodoApi` output has a `Description` property that provides a brief description of the output. In this case, it describes the output as the API Gateway endpoint URL for the `Prod` stage of the `TodoFunction` resource.

The `Value` property is a `!Sub` function that substitutes variables in a string with their corresponding values. In this case, it substitutes the `${ServerlessHttpApi}` variable with the name of the HTTP API that is created for the `TodoFunction` resource, and the `${AWS::Region}` and `${AWS::URLSuffix}` variables with the AWS region and URL suffix, respectively.

## AWS SAM Deploy <a name="step4"></a>

To start deploying our serverless application we can run this command in our terminal

```sh
sam build
sam deploy --guided
```

`sam deploy --guided` is a command provided by the AWS SAM (Serverless Application Model) CLI (Command Line Interface) that launches an interactive, guided deployment process for your serverless application. This command helps you deploy your application with AWS SAM in an easy and user-friendly manner.

When you run `sam deploy --guided`, you will be asked for some configurations input, which you can fill in like this.

```sh
Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Found
        Reading default arguments  :  Success

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: hapijs-todoapp
        AWS Region [ap-southeast-1]: ap-southeast-1
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: y
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: Y
        #Preserves the state of previously provisioned resources when an operation fails
        Disable rollback [y/N]: N
        ExpressFunction has no authentication. Is this okay? [y/N]: y
        ExpressFunction has no authentication. Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: Y
        SAM configuration file [samconfig.toml]: samconfig.toml
        SAM configuration environment [default]: default
```

AWS SAM will start looking for resources needed for deployment we defined in our template.

```sh
Looking for resources needed for deployment:

        Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-1aa02zp34lw9q
        A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False
         Image repositories: Not found.
         #Managed repositories will be deleted when their functions are removed from the template and deployed
         Create managed ECR repositories for all functions? [Y/n]:

        Saved arguments to config file
        Running 'sam deploy' for future deployments will use the parameters saved above.
        The above parameters can be changed by modifying samconfig.toml
        Learn more about samconfig.toml syntax at
        https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

eb03fb97f024: Pushed
421b36cec0ce: Pushed
85de5da90a78: Pushed
29f371b320ea: Pushed
f2ca56bec846: Pushed
7c5fbd8aa99c: Pushed
1b4d47b7a3e7: Pushed
a3a67812fd2d: Pushed
cb6798486542: Pushed
cb0076c98b2c: Pushed
8f0b8fdd795f: Pushed
todofunction-4014533baa38-v1: digest: sha256:105bc7c6ce5aadd80e2c6e2fb96a64f2dde41a24600499fc442ac64f7f7f52f0 size: 2623
```

Then SAM will start initiating deployment

```sh
Initiating deployment
=====================

TodoFunction has no authentication.
TodoFunction has no authentication.
        Uploading to hapijs-todoapp/1599f63c1d296a53c88fc482c7d09a71.template  1413 / 1413  (100.00%)


Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                                     LogicalResourceId                             ResourceType                                  Replacement
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                         ServerlessHttpApiApiGatewayDefaultStage       AWS::ApiGatewayV2::Stage                      N/A
+ Add                                         ServerlessHttpApi                             AWS::ApiGatewayV2::Api                        N/A
+ Add                                         TodoFunctionPetstorePermission                AWS::Lambda::Permission                       N/A
+ Add                                         TodoFunctionRole                              AWS::IAM::Role                                N/A
+ Add                                         TodoFunctionRootPermission                    AWS::Lambda::Permission                       N/A
+ Add                                         TodoFunction                                  AWS::Lambda::Function                         N/A
+ Add                                         TodoTable                                     AWS::DynamoDB::Table                          N/A
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
```

Then after completing the deployment, AWS SAM will return an output of the endpoint URL like this.

```sh
Outputs
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 TodoApi
Description         API Gateway endpoint URL for Prod stage for Todo function
Value               https://fr9mddpdg7.execute-api.ap-southeast-1.amazonaws.com/
```

We'll initiate testing our application using the output endpoint URL `https://fr9mddpdg7.execute-api.ap-southeast-1.amazonaws.com/`.

> For the next subsequent deployment or after modifying AWS SAM template, you can just run this command.

```sh
sam build
sam deploy
```

## Test <a name="step5"></a>

To verify the functionality of our Endpoint URL, we can execute the following commands to both send new data and retrieve the corresponding response:

```sh
$ curl -k -X POST \
> -H "Content-Type: application/json" \
> -d '{"title":"Deploy and Run Node.js Application on AWS Lambda"}' \
> https://fr9mddpdg7.execute-api.ap-southeast-1.amazonaws.com/todos

{"$metadata":{"httpStatusCode":200,"requestId":"KDTCSC5G29C07JE6TM0V4QH0VRVV4KQNSO5AEMVJF66Q9ASUAAJG","attempts":1,"totalRetryDelay":0}}
```

Subsequently, we can retrieve the data we previously posted by executing this command and inspecting the response:

```sh
$ curl -k  https://fr9mddpdg7.execute-api.ap-southeast-1.amazonaws.com/todos

{"response":[{"id":"1698140034538","iscompleted":false,"title":"Deploy and Run Node.js Application on AWS Lambda"}]}
```

The above commands demonstrate the successful deployment and execution of our Node.js application on AWS Lambda, connected to an Amazon DynamoDB database.

## Cleanup <a name="step6"></a>

To remove our serverless application deployed using AWS SAM, execute the following command:

```sh
sam delete
```

`sam delete` will delete an AWS SAM application by deleting the AWS CloudFormation stack, the artifacts that were packaged and deployed to Amazon S3 and Amazon ECR, and the AWS SAM template file.



## Conclusions <a name="step7"></a>

In this article, we discover that we can deploy and execute our Node.js application on AWS Lambda, leveraging the capabilities of the AWS Lambda Web Adapter.

## Check out my previous post

{% embed https://dev.to/aws-builders/serverless-scheduled-reporting-service-with-aws-eventbridge-lambda-sns-and-cdk-f6j %}