Local AWS development with SAM

We can run a local instance of our SAM stack for a given application without sending requests to the cloud. This is implemented through Docker.

The SAM CLI handles all the Docker-related tasks, such as pulling the required Lambda runtime images, creating containers, mounting your code and dependencies, and running your Lambda functions inside those containers.

Basic set up

Enter your project directory.

First build your SAM application:

sam build

We then run:

sam local start-api

If you have an API Gateway endpoint that you want to call over the local server. You will be able to call it after executing the above.

This will be indicated by:

If we want to invoke the function directly we use:

 sam local invoke [FunctionName]

Using environment variables

If you have an API key or database credentials, you are going to typically want to use different values dependent on environment.

Even if the values are the same accross environments, it’s a good idea to not call a secret when working locally since this request is billable.

In the example below I show how to set up environment variables for an API key locally and in production.

Create secret

Go to AWS SecretsManager and add the API key as a secret. This will be sourced in production.

Create local ENV file

These must be in JSON to work with SAM:

Local env

// local-env.json
{
  "FunctionName": {
    "API_KEY": "xxx-yyy-xxx",
    "NODE_ENV": "development"
  }
}

We save these to the root of the given function’s directory not at the global repo level.

Be sure to add this to .gitignore so that it does not become public

Update template.yaml

Every environment variable you intend to use, must exist in the template.yaml, otherwise it will not be sourced at runtime:

...
Resources:
  Properties:
    Environment:
      Variables:
        SECRET_ARN: "arn:aws:secretsmanager:eu-west-2:885135949562:secret:wakatime-api-key-X9oF3v",
        NODE_ENV: production
        API_KEY:
...

We go ahead and populate the values for production. But we leave the variable we use in development blank, since we don’t want it committed and we will source it at the SAM invocation. It still needs to exist though.

Pass in the environment variable at invocation:

sam local start-api --env-vars /home/thomas/repos/lambdas/wakatime-api/get-coding-stats/local-env.json

In production, the variables required will be automatically sourced from the template.yaml

Create handler within the Lambda itself

You are obviously going to need to distinguish between the different deployments when the Lambda executes. Here is an example helper function:

import * as AWS from "aws-sdk";

const secretsManager = new AWS.SecretsManager();

async function getApiKey(): Promise<string> {
  if (process.env.NODE_ENV === "production") {
    const response = await secretsManager
      .getSecretValue({ SecretId: process.env.SECRET_ARN as string })
      .promise();
    const secretValues = JSON.parse(response.SecretString as string);
    return secretValues.API_KEY;
  } else {
    return process.env.API_KEY as string;
  }
}