Carl Willimott - Tech Blog: Quick tips when working with Lambda in AWS Amplify

Quick tips when working with Lambda in AWS Amplify

October 12, 2022

6 min read

Back

If like me, you use AWS Amplify as part of your development workflow then these 3 points might be of some use to you.

Quick disclaimer - whilst I advocate the use of AWS Amplify for building a quick POC or MVP, I think that you might want to consider a different approach for larger more established applications. This is mainly because things can start to get complicated and harder to manage. That being said, I would definitely advocate it for someone who is new to AWS or just wants to build and deploy something quickly to prove whether your hypothesis is viable or not.

1. Use a Makefile to make your life easier

One of the benefits of Amplify is the CLI. There is a whole suite of commands to allow you to interface with your app from the command line. You can create and destroy resources without too much hassle and the best part is that is does a relatively good job at setting up all the permissions for you.

Need a new lambda function? The CLI has your back.

$ amplify function create

Decide you made a mistake and want to remove it? Once again a quick command will do what you need.

$ amplify function remove

Decide you want to run a function locally in a mocked environment? Okay this is where things start to get more complicated.

$ amplify mock function myLambdaFunction --event src/event.json

Imagine you have lots of functions and need to run them locally, quickly and efficiently, well why not make use of a Makefile.

Literally create a file called Makefile and you can define some commands (this should work on most if not all UNIX/BSD based systems including MacOS and Linux).

define run
	amplify mock function $(1) --event src/event.json
endef

run-my-func:
	$(call run,myLambdaFunction)

Assuming you set the file up correctly you can now run your lambda function by typing the following command in your terminal.

$ make run-my-func

In my experience, you will just need to add some new entries to this file for each command you want to run. This works best with commands you need to run often but are long and arduous to type out and seldom change.

2. Create your own shared package for Lambda functions

As you start creating multiple lambda functions, you will probably want to write some common code to share between them. For example this could be some adapter / wrapper functionality around 3rd party API's or even some custom functions for handling high-level domain specific business logic. If that sounds familiar, then you would probably benefit from creating a shared package which you can effectively install and deploy alongside each function.

Whilst you could create a new package in a separate repo and import, you might also want to consider adding it to your Amplify repo as it will help speed up the development process. Once you have created a new package with your package manager of choice, you just need to navigate to the lambda function you want to consume this package from.

Here is an example of what your package.json could look like.

{
  "name": "myLambdaFunction",
  "version": "2.0.0",
  "description": "Lambda function generated by Amplify",
  "main": "index.js",
  "license": "Apache-2.0",
  "dependencies": {
    "@my-org-name/shared": "file:../../../../../shared"
  },
  "devDependencies": {
    "aws-sdk": "^2.1055.0"
  }
}

As you can see I have created a package called @my-org-name/shared, and reference this by using the file: notation. This is just the relative patch to your package from the lambda (hence all the ../).

The only real downside to this approach is that you will need to ensure you continually bump the shared package version and do a build when you make changes.

In order to ensure that you always have the latest version of the package in your function you could employ a make command, something like as follows.

define build
	cd amplify/backend/function/$(1)/src && rm -rf node_modules && rm yarn.lock && yarn install && git add yarn.lock
endef

build-my-func:
	$(call run,myLambdaFunction)

As before, you just need to run the command like so.

$ make build-my-func

Use this with the command for running the function from the first part, and you have a simple and effective way to build and test your lambdas.

3. Expand your app permissions to give you access to other AWS resources

Amplify is great when you are getting started, out the box you have access to various resources such as Cognito, DynamoDB, S3 and Lambda to name a few, but what if you want to interface with other AWS services? A classic example might be to trigger a transcription job from a lambda function on an audio file. What you will find is that it's simple to use the CLI and AWS SDK to get you most of the way, but when you go to run your function in the cloud, you will be hit with an IAM permissions error.

Not to worry though, Amplify has your back. Each lambda function should have custom-policies.json file. In here you can you specify any custom access rules you require. The following example would give your function access to all transcribe method calls on all resources.

[
  {
    "Action": ["transcribe:*"],
    "Resource": ["*"]
  }
]

Disclaimer - You wouldn't want to give such permissive capabilities in a real world situation, but you can easily modify the policy to be as restrictive as possible.

Once you have added your policies here, you can run the following to generate the CloudFormation templates.

$ amplify push

This would generate the following policy for this lambda function, allowing you to go about your business like before. But seriously, please ensure you restrict your policies as much as possible!

{
   "CustomLambdaExecutionPolicy":{
      "Type":"AWS::IAM::Policy",
      "Properties":{
         "PolicyName":"custom-lambda-execution-policy",
         "PolicyDocument":{
            "Version":"2012-10-17",
            "Statement":[
               {
                  "Action":[
                     "transcribe:*"
                  ],
                  "Resource":[
                     "*"
                  ],
                  "Effect":"Allow"
               }
            ]
         },
         "Roles":[
            {
               "Ref":"LambdaExecutionRole"
            }
         ]
      },
      "DependsOn":"LambdaExecutionRole"
   }
}

Useful links