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
-
LocalStack:
- Create a LocalStack account here and follow the setup documentation for your environment (ensure Docker is installed).
-
AWS SAM CLI:
- Follow the installation guide for your OS from the official documentation.
-
SAM Local:
- Install
samlocal
to initialize, build, and deploy your application: GitHub - AWS SAM CLI Local
- Install
LocalStack : Setting Up
- Run the following command to initiate LocalStack:
localstack start
- 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 :
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
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
- 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
- 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
- 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
- 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
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;
}
}
- 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
}
Testing - Get and Add Flight
Initialize the template using SAM Local:
samlocal init --location template.yaml
Build the application:
samlocal build --template template.yaml
Deploy the application:
After a successful build, use the guided deploy:
samlocal deploy --guided
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"
}
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
Cleaning Up
To delete the environment, you can use this command :
samlocal delete
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)