Supercharge Your Local AWS Lambda Development: A Guide to Using LocalStack with the Serverless Framework

Supercharge Your Local AWS Lambda Development: A Guide to Using LocalStack with the Serverless Framework

Introduction

https://github.com/MohamedAljoke/estudo-localstack

LocalStack provides a fully functional local AWS cloud stack that simulates the AWS cloud environment, allowing developers to develop and test their serverless applications locally, without incurring any cloud costs. By leveraging LocalStack in conjunction with the Serverless Framework, developers can streamline their development workflow, iterate faster, and ensure the reliability of their applications before deploying them to the cloud.

Code

first create a docker-compose.yml with the localstack service.

#docker-compose.yml
version: '3.8'

services:
  localstack:
    image: localstack/localstack:latest
    ports:
      - '4566:4566'
    environment:
      - services=s3,sqs,lambda,logs,cloudformation,ec2,apigateway
      - DEFAULT_REGION=us-east-1
      - LAMBDA_EXECUTOR=docker
      - LAMBDA_REMOTE_DOCKER=true
      - LAMBDA_REMOVE_CONTAINERS=true
      - DATA_DIR=/tmp/localstack/data
      - DEBUG=1
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - '${TMPDIR:-/tmp/localstack}:/tmp/localstack'
      - '/var/run/docker.sock:/var/run/docker.sock'

start the container with

docker-compose up -d

now let´s create a simple nodejs handler with typescript

mkdir node_lambda
cd node_lambda
npm init -y
npm i typescript   @types/node  ts-node-dev  serverless --save-dev
npx tsc --init --outDir build
touch main.ts

inside main.ts add this code

//main.ts
export async function handler(event: any) {
  console.log('my_event', JSON.stringify(event));
  return 'Hello world';
}

now the handler is finished let´s configure the serverless to create the lambda and define the lambda function, the handler points to the build folder in the main file.handler function that is exported from it

#serverless.yml
service: my-service-name
provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  stage: ${opt:stage, 'local'}

functions:
  nodeLambdaHandler:
   handler: build/main.handler
   events:
    - http:
        path: /
        method: ANY

with this we can already send the lambda to aws, now lets add serverless-localstack so that we can deploy the stack localy

npx serverless plugin install -n serverless-localstack
#serverless.yml
service: my-service-name
provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  stage: ${opt:stage, 'local'}

#add this code to use serverless-localstack when deploy on local stage
plugins:
  - serverless-localstack
custom:
  localstack:
    stages:
      - local
    host: http://localhost
    edgePort: 4566

functions:
  nodeLambdaHandler:
   handler: build/main.handler
   events:
    - http:
        path: /
        method: ANY

let´s add few comands to the package.json

{
//....
  "scripts": {
    "build": "tsc",
    "deploy:local": "npm run build && npx serverless deploy --stage local",
    "logs": "sls logs -t -f nodeLambdaHandler --stage local"
  },
//....
}

so now if we do this comand it will send the lambda to the localstack

npm run deploy:local

you can run this comand to watch the lambda logs

npm run logs

we can add serverless offline to run the application

npx serverless plugin install -n serverless-offline

change the serverless.yml file

#serverless.yml
service: my-service-name
provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  stage: ${opt:stage, 'local'}

#add this code to use serverless-localstack when deploy on local stage
plugins:
  - serverless-localstack
######add this line ->
  - serverless-offline
custom:
  localstack:
    stages:
      - local
    host: http://localhost
    edgePort: 4566

functions:
  nodeLambdaHandler:
   handler: build/main.handler
   events:
    - http:
        path: /
        method: ANY

and then update package.json scripts

{
//....
  "scripts": {
    "build": "tsc",
    //add the start:dev
    "start:dev": "npm run build && sls offline",
    "deploy:local": "npm run build && npx serverless deploy --stage local",
    "logs": "sls logs -t -f nodeLambdaHandler --stage local"
  },
//....
}

we can make a bucket now and make the lambda trigger with file drop in the bucket, where we define resources. so the final form for our serverless.yml file is

service: serverless-localstack-app


provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  stage: ${opt:stage, 'local'}

plugins:
  - serverless-localstack
  - serverless-offline

custom:
  localstack:
    stages:
      - local
    host: http://localhost
    edgePort: 4566

functions:
  nodeLambdaHandler:
   handler: build/main.handler
   events:
    - s3:
        bucket: test-bucket-name
        event: s3:ObjectCreated:*
        rules:
          - suffix: .pdf
    - http:
        path: /
        method: ANY

resources:
  Resources:
    MyS3Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: test-bucket-name

aws cli helper commands, adding the endpoint so we use localstack

aws --endpoint-url=http://localhost:4566 lambda list-functions \
    --query "Functions[*].FunctionName" \
    --output text

aws --endpoint-url=http://localhost:4566 s3 ls

we can use this command to send a pdf file to the s3 bucket on localstack wish will trigger the lambda function

aws --endpoint-url=http://localhost:4566 \
  s3api put-object \
  --bucket test-bucket-name \
  --key dummyfile.pdf --body=dummyfile.pdf

the lambda will log the event with the s3 bucket data, and now you can handle the object as you want

#example of the log you will get
{
  "Records": [
    {
      "eventVersion": "2.1",
      "eventSource": "aws:s3",
      "awsRegion": "us-east-1",
      "eventTime": "2024-04-23T04:26:24.585Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": { "principalId": "AIDAJDPLRKLG7UEXAMPLE" },
      "requestParameters": { "sourceIPAddress": "127.0.0.1" },
      "responseElements": {
        "x-amz-request-id": "3ebb59a3",
        "x-amz-id-2": "eftixk72aD6Ap51TnqcoF8eFidJG9Z/2"
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "f29cf7eb",
        "bucket": {
          "name": "test-bucket-name",
          "ownerIdentity": { "principalId": "A3NL1KOZZKExample" },
          "arn": "arn:aws:s3:::test-bucket-name"
        },
        "object": {
          "key": "dummyfile.pdf",
          "sequencer": "0055AED6DCD90281E5",
          "size": 5,
          "eTag": "698dc19d489c4e4db73e28a713eab07b"
        }
      }
    }
  ]
}