Set Up a Local Serverless Environment with npm Packages

· 2 min · medium

Say you've written Serverless Framework IaC for several Lambdas behind API Gateway, and they read from and write to DynamoDB. You want to test that code locally while developing, and any other developer should get the same setup just by cloning the project and running it.

You also have beta, staging and production environments, and want a local one with its own config.

The full working example is on GitHub.

Plugins

Two Serverless plugins do the work:

Getting started

Install the plugins as dev dependencies:

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

Add them to the plugins section, which most Serverless templates already have. In TypeScript:

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

Run Serverless Offline with a stage, because parts of the IaC, such as the table name, expect one:

serverless offline --stage=local

If it starts, you'll see a list of API Gateway endpoints like these:

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

Any Lambda that calls DynamoDB will fail at this point. The local database doesn't exist yet, and the code doesn't know where to find it.

For the second part, wrap DocumentClient in a small function and use it everywhere. It points at the local emulator when the stage is 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: the region should come from your config, which is out of scope here.

Calling code then looks like this:

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

Then start the local database:

serverless dynamodb start --stage=local

Your Lambdas can now talk to DynamoDB. To look inside the emulator, install NoSQL Workbench for DynamoDB, go to Operation builder > + Add connection and select the DynamoDB local tab:

Seed data

Seeding the table on start is straightforward. Put the items in a JSON array:

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

Then reference the file in a DynamoDB config. Here it's only for the local stage, but it could cover other environments too:

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

Run:

serverless dynamodb start --migrate --stage=local

The table now contains the seed items, which you can check in NoSQL Workbench.

Trade-offs

The strength of this approach is that another developer can be up and running with three commands, using the IaC that already exists.

It can't emulate everything. Model schemas for API Gateway, for example, aren't supported. For those you'd want something more complete, such as LocalStack, whose paid versions add even more, such as IAM.