Skip to content

Instantly share code, notes, and snippets.

@m0un10
Last active July 13, 2020 11:33
Show Gist options
  • Save m0un10/a5b07b120df5319c0b0575e2a979e04c to your computer and use it in GitHub Desktop.
Save m0un10/a5b07b120df5319c0b0575e2a979e04c to your computer and use it in GitHub Desktop.

serverless-stack.com with SAM

...as an alternative to the Serverless Framework.

Key Differences

  • SAM templates result in Cloudformation stacks. This has a benefit of ensuring all the resoures are defined together and allows the management from a GUI.
  • SAM requires that IAM policies to be defined at the function level rather than globally as per the Serverless Framework. According to the SAM docs, this was a design decision to reduce the risk of functions being granted more permissions than they need.
  • Enabling CORS in SAM requires the specific allowed methods and headers to be provided. This is as opposed to Serverless Framework where it is just an on/off flag.
  • SAM supports parameters to be passed to the template at deploy/test-time. Serverless Framework achieves this with environment variables.
  • SAM does not have build plugins like the Serverless Framework.

Workarounds required for the serverless-stack.com tutorial

  • As SAM does not have plugins, it can't orchestrate the ES5 bundling that is done in Serverless Framework with the serveless-bundle plugin. Instead, this is achieved with the combination of node and webpack rather than relying on sam build.
  • As a side effect of the ES5 use, SAM requires the build to be done before being able to deploy or test code locally. This can be a cause for confusion when starting out as one may try to deploy or invoke and wonder why the code changes aren't being reflected. I got into the habit of building first with npm run-script build && sam deploy.
  • In SAM, when we set the DefaultAuthorizer: AWS_IAM in the Global section, it will break the OPTIONS request when the API is called from a browser. That is because the global flags sets authentication even for the OPTIONS request which the Serverless Framework doesn't do. This workaround in SAM is to set AddDefaultAuthorizerToCorsPreflight: false.

Limitation with SAM in relation to the serverless-stack.com tutorial

  • The unit testing chapter is currently incompatible with the SAM setup (needs more investigation).

Tutorial changes

The first 7 chapters (Introduction to Create a Cognito test user) are relevant irregardless of a choice of the Serverless Framework or SAM.

Chapter amendments: Set up the Serverless Framework backend project

When SAM is to be used instead of the Serverless Framework, chapter 8 (Set up the Serverless Framework) should be replaced with the steps below.

Create the node project.

mkdir notes-app-api
cd notes-app-api
npm init -y

The steps in the existing chapter 8 related to Install Node.js packages are the same. They are copied below for convenience.

npm install
npm install aws-sdk --save-dev
npm install uuid@7.0.3 --save

File changed

Commit: f71a1d7

A package.json
A package-lock.json

Chapter amendments: Add Support for ES6/ES7 JavaScript

When SAM is to be used instead of the Serverless Framework, chapter 9 (Add Support for ES6/ES7 JavaScript) should be replaced with the steps below.

The following webpack related steps were required as per https://github.com/graphboss/aws-sam-webpack-plugin (more info here also)

npm install webpack webpack-cli aws-sam-webpack-plugin @types/aws-lambda --save-dev
npm install source-map-support --save

Create webpack.config.js

const path = require("path");
const AwsSamPlugin = require("aws-sam-webpack-plugin");

const awsSamPlugin = new AwsSamPlugin();

module.exports = {
  // Loads the entry object from the AWS::Serverless::Function resources in your
  // SAM config. Setting this to a function will
  entry: () => awsSamPlugin.entry(),

  // Write the output to the .aws-sam/build folder
  output: {
    filename: (chunkData) => awsSamPlugin.filename(chunkData),
    libraryTarget: "commonjs2",
    path: path.resolve(".")
  },

  // Create source maps
  devtool: "source-map",

  // Resolve .js extensions
  resolve: {
    extensions: [".js"]
  },

  // Target node
  target: "node",

  // AWS recommends always including the aws-sdk in your Lambda package but excluding can significantly reduce
  // the size of your deployment package. If you want to always include it then comment out this line. It has
  // been included conditionally because the node10.x docker image used by SAM local doesn't include it.
  externals: process.env.NODE_ENV === "development" ? [] : ["aws-sdk"],

  // Set the webpack mode
  mode: process.env.NODE_ENV || "production",

  // Add the AWS SAM Webpack plugin
  plugins: [awsSamPlugin]
};

File changed

Commit: b22062b

A webpack.config.js
M package.json
M package-lock.json

The next chapter (Initialize the backend repo) is relevant irregardless of the use of SAM or Serverless Framework.

Chapter amendments: Add a Create Note API

The steps related to the creation of javascript files (i.e. create.js, libs/dynamodb-lib.js and libs/handler-lib.js) and the mock (i.e. mocks/create-event.json) in the original chapter are unchanged.

The key difference for SAM are:

  1. we create a template.yaml (instead of the serverless.yml for the Serverless Framework)
  2. we need to build before we can test (instead of the Serverless Framework doing this automatically for us)
  3. the test invocation command is slightly different for SAM

The template.yaml is as follows:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: This is an AWS SAM fork of the popular Serverless Notes API

Parameters:
  NotesTableName:
    Type: String
    Default: "notes"
    Description: Notes Table Name

Globals:
  Api:
    Cors:
      AllowMethods: "'HEAD,OPTIONS,DELETE,GET,PUT,POST'"
      AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
      AllowOrigin: "'*'"
    Auth:
      DefaultAuthorizer: AWS_IAM
      AddDefaultAuthorizerToCorsPreflight: false
  Function:
    CodeUri: .
    Runtime: nodejs12.x
    Timeout: 10
    Environment:
      Variables:
        tableName: !Ref NotesTableName

Resources:
  Create:
    Type: AWS::Serverless::Function
    Properties:
      Handler: create.main
      Policies:
        - Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Action:
                - 'dynamodb:PutItem'
              Resource: 'arn:aws:dynamodb:us-east-1:*:*'
      Events:
        HTTP:
          Type: Api
          Properties:
            Path: /notes
            Method: post

Outputs:
  NotesApi:
    Description: "API Gateway endpoint URL for Prod stage for Notes function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/notes/"

Unlike the Serverless Framework, we have to build our code first. Let's update our package.json so that it has the ability to do our build

  "scripts": {
    "build": "webpack-cli",
    "clean": "rimraf .aws-sam",
    "prebuild": "rimraf .aws-sam",
    "prewatch": "rimraf .aws-sam",
    "watch": "webpack-cli -w"
  },

Now we can build our code with npm run-script build

This will result in the javascript being built under .aws-sam/build

Now we can invoke the function with sam local invoke --parameter-overrides ParameterKey=NotesTableName,ParameterValue=notes --profile myProfile Create -e mocks/create-event.json

File changed

Commit: b22062b

A template.yaml
A create.js
A libs/dynamodb-lib.js
A libs/handler-lib.js
A mocks/create-event.json
M package.json

Chapter amendments: Add a get note API

Requires Get function in template.yml

Chapter amendments: Add a list all the notes API

Requires List function in template.yml

Chapter amendments: Add an update note API

Requires Update function in template.yml

Chapter amendments: Add a delete note API

Requires Delete function in template.yml

The Working with 3rd party APIs and Setup a Stripe account chapters don't need to change.

Chapter amendments: Add a billing API

Requires Billing function in template.yml

Chapter amendments: Load secrets from .env

In addition to SAM's support for passing arguments at deploy-time, it can also take a JSON file with the specific environment variables when calling the invoke command.

Chapter amendments: Test the billing API

Now we can invoke the function with sam local invoke --parameter-overrides ParameterKey=NotesTableName,ParameterValue=notes ParameterKey=StripeSecretKey,ParameterValue=<SecretKey> --profile myProfile Billing -e mocks/billing-event.json

More investigation is needed for the chapters Unit Tests in Serverless and Handle API Gateway CORS Errors.

Chapter amendments: Deploy the APIs

sam deploy --guided

Resources

The full working SAM project is available at https://github.com/m0un10/sam-stack-demo-api/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment