Setup Local Serverless Environment with npm packages

Create a IaC based serverless emulation using npm packages

Scenario

Setup not related to this article will be skipped for brevity.

You have written some Serverless IaC and have created several Lambdas which need to either read or write to DynamoDB, the Lambdas are proxied through an API gateway. You want the ability to test your code locally whilst developing functionality and easily give other developers the same ability just by cloning and running the project.

You have different environments (beta/staging/production) and would like an env for local development to easily use specific configs.

An example of this all working can be found here ๐Ÿ‘‰ https://github.com/ash-grennan/serverless-offline-with-dynamodb.

Plugins

For this we’re going to install several Serverless plugins, these provide additional functionality and cater for many common scenarios that may arise during development or simply enhancing IaC.

Serverless Offline - Used to locally run Lambdas and API Gateway, will provide an endpoint per Lambda and supports hot reloading during development.

Serverless DynamoDB Local - This will create an emulation of DynamoDB locally which is required for the Lambdas in this scenario.

It’s also worth mentioning that DynamoDB local will require JRE which can be found here

Getting Started

First, you’ll want to install those plugins as dev dependencies, so:

npm i -save-dev serverless-offline serverless-dynamodb-local

Next, you’ll want to locate the plugins section (this is present in most Serverless templates) and add those plugins, it should look like this in TypeScript:

  service: "serverless-offline-with-dynamodb",
  frameworkVersion: "2",
  plugins: [
    "serverless-esbuild",
    "serverless-offline",
    "serverless-dynamodb-local",
  ],
  ...

Awesome, now we’ll run our Serverless offline plugin, we’ll pass in a stage environment since some of our IaC expects a stage variable such as our table.

serverless offline --stage=local

If the offline plugin has run the Serverless successfully, you’ll get a list of API GW endpoints similar to the below:

GET  | http://localhost:3000/local/api/customer/{id}
POST  | http://localhost:3000/local/api/customer

Now, if the Lambda invoked requires access to DynamoDB, you’ll receive an error, this is because we need to create our local db instance and point our calling code to the local address of this emulation, let’s do that now.

To do this, we’ll wrap DocumentClient in an abstraction and simply use this in any calling code, within this facade we’ll simply use a condition to change the DynamoDB location depending if it’s local.

const getClient = (stage: string) : DocumentClient => {
    if (stage == "local") {
        return new DocumentClient({
            region: 'localhost',
            endpoint: 'http://localhost:8000'
          })
    }
    return new DocumentClient({ region: "eu-west-2" });
}

export default getClient; 

Side note: region should be from your config, we’re skipping some steps since its out of scope for this article

And then our calling code would look like:

const documentClient = createClient(process.env.ENVIRONMENT);

We can now run the following command:

serverless dynamodb start --stage=local

If everything has been run correctly, your Lambda will now be able to speak to your DynamoDB, we can look at our emulation via installing NoSQL Workbench for DynamoDB

Once installed, go Operation builder > + Add Connection and select DynamoDB local tab, it should look like the below:

Seed Data

Fortunately, it’s trivial to add seed data as part of our start command for DynamoDB, create a JSON file and add the JSON objects to an array:

[
    {
        "customerId": "ec3808fb-ecb7-48b6-8d82-67529ad0c6ef",
        "firstName": "Ash",
        "lastName": "Grennan"
    },
    ...
]

Next, we’ll want to define a DynamoDB config, in our scenario this will only be used for our local environment but could be extended to include additional configuration across many environments.

    seed: {
      local : {
        sources: {
          table: "local-customers",
          sources: ["./seed-data.json"]
        }
      }
    }

Then run the below:

serverless dynamodb start --migrate --stage=local

The table will now contain JSON objects which can be verified by NoSQL Workbench.

Conclusion

One of the strengths of this approach is another developer can simply run 3 terminal commands and be running immediately whilst leveraging the existing IaC.

Naturally, there are drawbacks, for instance, Serverless offline cannot emulate everything, an example of this would be schema definitions for API GW models, in which case you’d want something more robust such as LocalStack, which license versions providing even more functionality such as IAM.

In case you missed above, here’s a GitHub link link to the example ๐Ÿ‘Š.

Ash Grennan
Ash Grennan
Snr Software Engineer

Deliver value first, empower teams to make technical decisions. Snr Engineer @ Moonpig, hold a BSc & MSc in software engineering & certified AWS Solutions Architect (LinkedIn). A fan of Serverless computing, distributed systems, and anything published by serverless.com ๐Ÿงก