Set Up a Local Serverless Environment with npm Packages
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:
- Serverless Offline runs Lambdas and API Gateway locally. It gives each Lambda an endpoint and hot-reloads during development.
- Serverless DynamoDB Local runs a local emulation of DynamoDB, which these Lambdas need. It requires a Java runtime (JRE).
Getting started
Install the plugins as dev dependencies:
npm i --save-dev serverless-offline serverless-dynamodb-localAdd 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=localIf 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=localYour 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=localThe 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.