Building and Integrating Lambda Layers with Serverless API
Tags: aws, lambda, serverless, api
Published: 2024-07-12 12:05:44

In this tutorial, we’ll explore how to use AWS Lambda Layers to organize and reuse code across multiple AWS Lambda functions. Specifically, we’ll move DynamoDB queries to a separate Lambda Layer and integrate it with a Serverless API. This approach enhances maintainability and promotes code reuse, making your serverless applications more scalable and efficient.

**Overview**

Our solution involves creating a Lambda Layer containing DynamoDB operations (list, add, and get countries) and integrating this layer with our existing Serverless API. We will define the required resources using the Serverless Framework and deploy them to AWS.

**Architecture**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/at3jjthto1inb2p3nehi.png)
The architecture includes:
- A DynamoDB table to store country information.
- A Lambda Layer to encapsulate DynamoDB operations.
- Multiple Lambda functions to handle API requests (add, list, get countries).
- API Gateway to expose these Lambda functions as RESTful endpoints.

**Directory Structure**

Here is the directory structure for our project:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fk08njdcpcrpb2ui5zlh.png)

**Step-by-Step Guide**
**1. Create a Separate Folder for Lambda Layers**
Create a folder named Dynamo_layer and add the following:
- A folder named modules-layer.
- A folder named dynamo-sdk-layer.
- A serverless.yml file.

**2. Add Layers Resources in serverless.yml**
In the Dynamo_layer directory, add the following content to serverless.yml:

```
# Service name has to be unique for your account.
service: my-dependencies

# framework version range supported by this service.
frameworkVersion: ">=1.1.0 <8.0.0"

# configuration of the cloud provider. As we are using AWS so we defined AWS corresponding configuration.
provider:
  name: aws
  lambdaHashingVersion: 20201221
  runtime: nodejs14.x
  #stage: dev
  region: us-east-2
  # Create an ENV variable to be able to use it in my JS code. *** Check line 4 in get-country-by-name JS file ***
  environment:
    tableName: ${self:custom.tableName}
  # To Give a permission to each lambda function to access DynamoDB table
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:*
        # - dynamodb:Query
        # - dynamodb:Scan
        # - dynamodb:GetItem
        # - dynamodb:PutItem
      Resource: "*"
  
layers:
  ModulesLayer:
    path: ./modules-layer
    description: "my dependencies"
  
  DynamoSdkLayer:
    path: ./dynamo-sdk-layer
    description: "my dynamo dependencies"

custom:
  tableName: countries
```
**3. Deploy Lambda Layers**

Deploy the Dynamo_layer to create a CloudFormation stack for Lambda Layers by running the following command:

```
sls deploy

```
**4. Navigate to the my-service folder, which contains the serverless.yml file for your API Gateway endpoints and Lambda functions.** 
Ensure the configuration includes:
- A DynamoDB table.
- Lambda functions for countries_loadData, add-country, list-countries, and get-country-by-name.

serverless.yml

```
# Service name has to be unique for your account.
service: my-service

# framework version range supported by this service.
frameworkVersion: ">=1.1.0 <8.0.0"

# configuration of the cloud provider. As we are using AWS so we defined AWS corresponding configuration.
provider:
  name: aws
  lambdaHashingVersion: 20201221
  runtime: nodejs14.x
  #stage: dev
  region: us-east-2
  # Create an ENV variable to be able to use it in my JS code. *** Check line 4 in get-country-by-name JS file ***
  environment:
    tableName: ${self:custom.tableName}
    NODE_PATH: "./:/opt/node_modules"
  # To Give a permission to each lambda function to access DynamoDB table
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:*
        # - dynamodb:Query
        # - dynamodb:Scan
        # - dynamodb:GetItem
        # - dynamodb:PutItem
      Resource: "*"
  
# package:
#   exclude:
#     - Dynamo-SDK/**

custom:
  tableName: countriees         

# As shown below, when the HTTP POST or GET request is made, the handler should be invoked.
functions:

#(1) Lambda function to initially fill DynamoDB
  FillDynamoDB:
    handler: lambdas/countries_loadData.fill
    description: fill DynamoDB table with set of countries.
    events:
      - http: 
          path: fill-dynamoDB
          method: POST
          cors: true
    layers:
      - arn:aws:lambda:us-east-2:<ACCOUNT-ID>:layer:ModulesLayer:3
      - arn:aws:lambda:us-east-2:<ACCOUNT-ID>:layer:DynamoSdkLayer:3

#(2) Lambda function to list all the countries 
  GetAllCountries:
    handler: lambdas/list-countries.list
    description: get all the countries information.
    events:
      - http: 
          path: list-countries
          method: GET
          cors: true
    layers:
      - arn:aws:lambda:us-east-2:<ACCOUNT-ID>:layer:ModulesLayer:3
      - arn:aws:lambda:us-east-2:<ACCOUNT-ID>:layer:DynamoSdkLayer:3

#(3) Lambda function to get a country by name
  GetCountryByName:
    handler: lambdas/get-country-by-name.get
    description: get country By Name.
    events:
      - http: 
          path: get-country/{NAME}
          method: GET
          cors: true
    layers:
      - arn:aws:lambda:us-east-2:<ACCOUNT-ID>:layer:ModulesLayer:3
      - arn:aws:lambda:us-east-2:<ACCOUNT-ID>:layer:DynamoSdkLayer:3

#(4) Lambda function to add a new country 
  AddNewCountry:
    handler: lambdas/add-country.add
    description: add a new country.
    events:
      - http: 
          path: add-country
          method: POST
          cors: true
    layers:
      - arn:aws:lambda:us-east-2:<ACCOUNT-ID>:layer:ModulesLayer:3
      - arn:aws:lambda:us-east-2:<ACCOUNT-ID>:layer:DynamoSdkLayer:3



#Resources are AWS infrastructure components which your Functions use. 
#The Serverless Framework deploys an AWS components your Functions depend upon.
resources:
  Resources:
    myDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      #DeletionPolicy: Retain
      Properties:       
        TableName: ${self:custom.tableName}

        AttributeDefinitions:
          -
            AttributeName: "NAME"
            AttributeType: "S" 

        KeySchema:
          -
            AttributeName: "NAME"
            KeyType: "HASH"

        BillingMode: PAY_PER_REQUEST
    
    # Custom resource to invoke lambda function to fill the countries DynamoDB table
    TriggerFillDynamoDBFunction:
     Type: AWS::CloudFormation::CustomResource
     Properties:
       ServiceToken: !GetAtt 'FillDynamoDBLambdaFunction.Arn'
```
**5. Deploy Serverless API**

Deploy the serverless configuration by running:

```
sls deploy

```
**6. Access API Endpoints**

You can access the endpoints using the following URLs:

List countries: GET

```
https://nymaeazapc.execute-api.us-east-2.amazonaws.com/dev/list-countries
```
Get country by name: GET

```
https://nymaeazapc.execute-api.us-east-2.amazonaws.com/dev/get-country/{NAME}
```
Add a country: POST

```
https://nymaeazapc.execute-api.us-east-2.amazonaws.com/dev/add-country

```
**7. Remove All Functions, Events, and Resources**

To clean up, remove all AWS resources created by running:

```
sls remove

```
**Conclusion**

By following these steps, you have successfully utilized AWS Lambda Layers to modularize and reuse DynamoDB queries across multiple Lambda functions. This approach demonstrates the power of Lambda Layers in promoting code reuse and maintainability, making your serverless applications more efficient and scalable.