Setup Local Serverless Environment with npm packages
Create a IaC based serverless emulation using npm packages
Scenario
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 ๐.