Starting AWS SAM folder structure (NodeJS)

12. November 2020

When I started putting together my first AWS SAM project, I was confused with the project structure - as always, when I’m starting new project with new technology. You can easily make a bloated project where code is duplicated in each lambda function.

I had a few goals to achieve:

  • simple and general structure which is a good starting point for smaller applications
  • each lambda function has its own dependencies
  • common code is shared in layers
  • no external dependencies in the repository - every dependency is defined in package.json

In the project folder, I created folder Lambdas. Each lambda is in its own folder with package.json. The package.json contains dependencies only required by this function.

Folder Layers will contain folders with layers. Because the structure is just a starting point, I will have only two layers. One called SharedInternalDependencies with my own library code which I want to share and one called SharedExternalDependencies with third-party libraries used in lambdas and in internal shared dependencies.

SharedExternalDependencies just contain package.json. Dependencies will be downloaded by SAM during the build process. Yes, SAM can build not only lambdas but also layers. But you should use the newest version of SAM, because this feature wasn’t always there.

SharedInternalDependencies is not built by SAM, so we have to create a valid folder structure for Node. The correct structure is nodejs/node_modules/my_module. Or nodejs/node_modules/my_module.js. If you use a different language, the path has to be different. You can find the correct paths in the AWS documentation.

The final structure will look like this:
 

  • MySAMProject
    • template.yml
    • Lambdas
      • Lambda-1
        • lambda-1.js
        • package.json
      • Lamda-2
        • lambda-2.js
        • package.json
    • Layers
      • SharedInternalDependencies
        • nodejs
          • node_modules
            • my-library-module.js
      • SharedExternalDependencies
        • package.json
The last step is to configure lambdas and layers in template.yml. My example will have these 4 resources.
  1. lambda1:
  2.     Type: AWS::Serverless::Function
  3.     Properties:
  4.       CodeUri: Lambdas/Lambda-1/
  5.       Handler: lambda-1.myExportedFunction
  6.       Runtime: nodejs12.x
  7.       MemorySize: 128
  8.       Timeout: 100
  9.       Description: My first lambda function.
  10.       Layers:
  11.         - !Ref externalDep
  12.         - !Ref internalDep
  13.         
  14.   lambda2:
  15.     Type: AWS::Serverless::Function
  16.     Properties:
  17.       CodeUri: Lambdas/Lambda-2/
  18.       Handler: lambda-2.myExportedFunction
  19.       Runtime: nodejs12.x
  20.       MemorySize: 128
  21.       Timeout: 100
  22.       Description: Second lambda.
  23.       Layers:
  24.         - !Ref externalDep
  25.         - !Ref internalDep
  26.   
  27.   externalDep:
  28.     Type: AWS::Serverless::LayerVersion
  29.     Properties:
  30.         LayerName: SharedExternalDependencies
  31.         Description: Shared external dependencies.
  32.         ContentUri: Layers/SharedExternalDependencies/.
  33.         CompatibleRuntimes:
  34.             - nodejs12.x
  35.         RetentionPolicy: Retain
  36.     Metadata:
  37.         BuildMethod: nodejs12.x
  38.   
  39.   internalDep:
  40.     Type: AWS::Serverless::LayerVersion
  41.     Properties:
  42.         LayerName: SharedInternalDependencies
  43.         Description: Shared internal dependencies.
  44.         ContentUri: Layers/SharedInternalDependencies/.
  45.         CompatibleRuntimes:
  46.             - nodejs12.x
  47.         RetentionPolicy: Retain

Notice the definition of external layer has Metadata. The selected BuildMethod will build the layer. If you open .aws-sam, you can notice that the correct path nodejs/node_modules with downloaded modules was created.

Author: Luděk Novotný