Why Local Lambda Testing Matters

Developers using the Serverless framework with AWS API Gateway and Lambda know the drill: every new endpoint requires a full API deployment before it can be tested. That means waiting for CloudFormation to chew through layers, functions, and API resources, and then still hitting a separate deploy step for each code change.

The cost adds up fast. In a recent project, the average deploy time was roughly:

  • Single endpoint: ~7 seconds
  • Full API (~12 resources): ~24 seconds
  • Layers (2 layers): ~32 seconds

Over weeks of iterative development, those minutes turn into hours of dead time. The solution is to run Lambda functions locally, where feedback is instant and debugging tools like Visual Studio Code’s breakpoints work normally.

Local Data and Environment Handling

When running code locally, consider your data source. The example project stores data as JSON files in S3; a local snapshot of those files can stand in when the function runs on your machine. If your API reads from a database, you may need a local instance or a similar equivalent. Every project has unique constraints, so the pattern below is a starting point, not a prescription.

To make the Lambda function aware it’s running locally, introduce an environment variable — here called LOCAL_DEV — and check it in your code:

const data = 
  process.env.LOCAL_DEV === "true"
  ? require(`./data/tacos.json`)
  : //handle loading/setting the data as you regularly would

Note that the boolean value is a string; environment variables always arrive as strings, so handle the type conversion in your logic.

If you use Lambda layers, you’ll also need to adjust the require path at the top of your function file. Instead of referencing the layer by its logical name, point directly to the source directory:

const apiCommon = process.env.LOCAL_DEV === "true"
? require("../layers/apicommon/nodejs/node_modules/apicommon/index")
: require("apicommon");

Invoking a Function Locally

With the code ready, serverless invoke local runs the function without any cloud dependency. The example below is for the tacos endpoint, which accepts query string parameters that describe the tacos to fetch. The full command is defined in package.json, where all quotes need literal backslash escapes:

"scripts": {
"local-tacos": "serverless invoke local --function tacos --data '{ \"queryStringParameters\": {\"type\": \"breakfast\", \"filling1\": \"egg\", \"filling2\": \"bacon\", \"filling3\": \"cheese\", \"tortilla\": \"flour\", \"salsa\": \"Salsa Doña\"}}' -e LOCAL_DEV=true > output.json"
}

Stripping out those escape markers makes the command much easier to read:

serverless invoke local --function tacos --data '{ "queryStringParameters": {"type": "breakfast", "filling1": "egg", "filling2": "bacon", "filling3": "cheese", "tortilla": "flour", "salsa": "Salsa Doña"}}' -e LOCAL_DEV=true > output.json

The first part invokes the Lambda locally:

serverless invoke local --function tacos

The second part passes query parameters. Here the request asks for tacos with egg, bacon, and cheese on a flour tortilla with Salsa Doña:

--data '{ "queryStringParameters": {"type": "breakfast", "filling1": "egg", "filling2": "bacon", "filling3": "cheese", "tortilla": "flour", "salsa": "Salsa Doña"}}'

If you have many parameter sets to check, write them to a JSON file and use it in the invocation:

yarn local-taco query-success yarn local-taco query-fail

The third part sets environment variables, signaling to the Lambda that it’s running in local development mode:

-e LOCAL_DEV=true

Finally, pipe the output to a JSON file for quick inspection:

> output.json

That way you can immediately confirm whether the results are correct or an error was thrown.

A Usable Pattern

Locally invoked Lambda functions give you the fast feedback loop that cloud deploys lack, especially during early API development. A sample project demonstrating the complete setup — Serverless, API Gateway, and Lambda — is available on GitHub.