DEV Community

Axel
Axel

Posted on

Deploying AWS Lambda Functions with API Gateway to DynamoDB using AWS SAM on LocalStack

Introduction

  • LocalStack: A fully functional local AWS cloud stack, enabling you to test and develop cloud applications offline without incurring AWS costs.
  • AWS SAM (Serverless Application Model): A framework for building serverless applications, simplifying the setup of Lambda functions, APIs, and S3.
  • AWS Lambda: A serverless compute service provided by Amazon Web Services (AWS) that allows you to run code without provisioning or managing servers.

Prerequisites

  1. LocalStack:

    • Create a LocalStack account here and follow the setup documentation for your environment (ensure Docker is installed).
  2. AWS SAM CLI:

    • Follow the installation guide for your OS from the official documentation.
  3. SAM Local:


LocalStack : Setting Up

  • Run the following command to initiate LocalStack:
   localstack start
Enter fullscreen mode Exit fullscreen mode

localstack on ubuntu

  • Verify LocalStack Instance: Go to the LocalStack's website UI (https://app.localstack.cloud), and under the "Status" section, check if the instance and services are running : localstack'web ui

localstack region


AWS SAM : Setting up

The AWS SAM template provides information about the stack that we want to deploy on LocalStack, typically defined in template.yaml.

  • Here some parameters that use in the template :
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Parameters:
  Stage:
    Type: String
    Description: Stage name
    AllowedValues:
      - dev
      - stage
      - production
    Default: dev
  LocalStackURL:
    Type: String
    Default: https://localhost.localstack.cloud:4566    
  TableName:
    Type: String
    Default: Activities
  Region:
    Type: String
    Default: eu-west-2
  AWSEnv:
    Type: String
    Default: AWS
Enter fullscreen mode Exit fullscreen mode

We need to create 3 resources :

  • DynamoDB Table (Activities)
Resources:
  Activities:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: !Ref TableName
      AttributeDefinitions:
        - AttributeName: id
          AttributeType: N
      KeySchema:
        - AttributeName: id
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
Enter fullscreen mode Exit fullscreen mode
  • InsertFlight Lambda Function - Used to insert information into the DynamoDB table
  InsertFlight:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: java11
      Handler: lambda.InsertFlight::handleRequest
      CodeUri: ./javaInsertFlight
      MemorySize: 1024
      Timeout: 300
      Description: 'Lambda function to insert data to DynamoDB'
      Tracing: Active
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref TableName
      Environment:
        Variables:
          TABLE: !Ref TableName
          REGION: !Ref Region
          LOCALSTACKURL: !Ref LocalStackURL
          AWSENV: !Ref AWSEnv
Enter fullscreen mode Exit fullscreen mode
  • GetFlight Lambda Function - Used to retrieve the flight by its ID
  GetFlight:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.10
      Handler: app.lambda_handler
      CodeUri: pythonGetFlight
      MemorySize: 1024
      Timeout: 300
      Description: 'Lambda function to retrieve data from DynamoDB'
      Tracing: Active
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref TableName
      Environment:
        Variables:
          TABLE: !Ref TableName
          REGION: !Ref Region
          LOCALSTACKURL: !Ref LocalStackURL          
          AWSENV: !Ref AWSEnv          
Enter fullscreen mode Exit fullscreen mode
  • API Gateway - The ApiEvent resource creates an API Gateway to invoke the GetFlight and InsertFlight Lambda functions
  ApiEvent:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref Stage
      DefinitionBody:
        swagger: "2.0"
        info:
          title: "API for Lambda functions"
          version: "1.0"
        paths:
          /flight:
            post:
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${InsertFlight.Arn}/invocations
                httpMethod: POST
                type: aws_proxy
          /flight/{id}:
            get:
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetFlight.Arn}/invocations
                httpMethod: GET
                type: aws_proxy
Enter fullscreen mode Exit fullscreen mode
  • Outputs After deployment, the following outputs will provide useful information for invoking the API and Lambda functions.
Outputs:
  SAMExampleRegion:
    Description: "Region where we launch the AWS SAM template"
    Value: !Sub "The region is ${AWS::Region}"
  SAMExampleApi:
    Description: "API Gateway endpoint URL for the stage"
    Value: !Sub http://${ApiEvent}.execute-api.localhost.localstack.cloud:4566/${Stage}/flight
  SAMExampleInsertFlightFunction:
    Description: "InsertFlight Lambda Function ARN"
    Value: !GetAtt InsertFlight.Arn
  SAMExampleGetFlightFunction:
    Description: "GetFlight Lambda Function ARN"
    Value: !GetAtt GetFlight.Arn
Enter fullscreen mode Exit fullscreen mode

AWS Lambda Function

For these example, i have create an InsertFlight write in JAVA and GetFlight write in Python.

  • InsertFlight - JAVA
public class InsertFlight implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
    public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent apiGatewayProxyRequestEvent, Context context) {

        // Set up the credentials
        BasicAWSCredentials awsCreds = new BasicAWSCredentials("accessKey", "secretKey");

        // Create DynamoDB client configured for LocalStack
        // Build the AmazonDynamoDB client to connect to LocalStack
        AmazonDynamoDB dynamoDBClient = AmazonDynamoDBClientBuilder.standard()
                .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(System.getenv("LOCALSTACKURL"), System.getenv("REGION")))
                .withCredentials(new AWSStaticCredentialsProvider(awsCreds))
                .build();

        DynamoDB dynamoDB = new DynamoDB(dynamoDBClient);
        Table table = dynamoDB.getTable(System.getenv("TABLE"));
        table.putItem(Item.fromJSON(apiGatewayProxyRequestEvent.getBody()));

        context.getLogger().log( "data saved successfully to dynamodb:::");
        return createAPIResponse(String.valueOf(Item.fromJSON(apiGatewayProxyRequestEvent.getBody())), 201);
    }

    private APIGatewayProxyResponseEvent createAPIResponse( String body, int statusCode) {
        APIGatewayProxyResponseEvent responseEvent = new APIGatewayProxyResponseEvent();
        responseEvent.setBody( body );
        responseEvent.setStatusCode( statusCode );
        return responseEvent;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • GetFlight - Python
import boto3
import os
import json


region = os.getenv('REGION')
localstackurl = os.getenv('LOCALSTACKURL')

dynamoDBClient = boto3.client('dynamodb', 
             endpoint_url=localstackurl, 
             aws_access_key_id='test', 
             aws_secret_access_key='test', 
             region_name=region)

dynamodb = boto3.resource('dynamodb', endpoint_url=localstackurl, region_name=region)

def lambda_handler(event, context):

    print("Start de lambda handler")

    item_id = event.get('pathParameters', {}).get('id')
    if not item_id:
        return {
            'statusCode': 400,
            'body': json.dumps({'error': 'ID not provided in path parameters'})
        }

    table_name = os.getenv('TABLE')
    if not table_name:
        raise KeyError("TABLEName environment variable is not set")

    print("GOT Table name : ", table_name)
    table = dynamodb.Table(table_name)

    # fetch todo from the database
    response = table.get_item(Key={'id': int(item_id)})
    print("GOT response : ", response)    
    item = response.get('Item', {})
    print("GOT item : ", item)    

    print("Fin de lambda handler with context", context)

    return {
        'statusCode': 200,
        'headers': {},
        'body': item
    }
Enter fullscreen mode Exit fullscreen mode

Testing - Get and Add Flight

Initialize the template using SAM Local:

samlocal init --location template.yaml
Enter fullscreen mode Exit fullscreen mode

Build the application:

samlocal build --template template.yaml
Enter fullscreen mode Exit fullscreen mode

Deploy the application:
After a successful build, use the guided deploy:

samlocal deploy --guided
Enter fullscreen mode Exit fullscreen mode

When all these steps are been done, you have the ouput with the link to call the api gateway.

To add some data, you can do :

POST http://26riz6gpm6.execute-api.localhost.localstack.cloud:4566/dev/flight

With the payload :

{
"id": 1,
"Name": "Test_name"
}
Enter fullscreen mode Exit fullscreen mode

To retrieve the data, you can use this GET url with the id 1 :

GET http://26riz6gpm6.execute-api.localhost.localstack.cloud:4566/dev/flight/1
Enter fullscreen mode Exit fullscreen mode

Cleaning Up

To delete the environment, you can use this command :

samlocal delete
Enter fullscreen mode Exit fullscreen mode

Conclusion

Deploying AWS Lambda functions with API Gateway and DynamoDB using AWS SAM on LocalStack offers a powerful and cost-effective solution for local development of serverless applications. By simulating the AWS environment on your local machine, you can rapidly iterate on your application, test integrations, and ensure that the entire workflow functions as expected before deploying to the actual AWS cloud.

This guide walked through the process of setting up LocalStack and AWS SAM, defining the necessary resources, and deploying Lambda functions locally. By using LocalStack, developers can avoid the potential pitfalls of cloud development costs and minimize time spent waiting for deployments. With this setup, you can focus on building, testing, and refining your serverless application efficiently.

Now, you're well-equipped to take your serverless projects from local development to production deployment with confidence.

Top comments (0)