Serverless AWS Lambda
Plug & Play AWS Lambda NodeJS development tool with local API Gateway and Application Load Balancer support.
# serverless-aws-lambda
[](https://nodei.co/npm/serverless-aws-lambda/)
### Description
> AWS Lambda dev tool for Serverless. Allows Express synthax in handlers. Supports packaging, local invoking and offline Application Load Balancer and API Gateway lambda server mocking.
- Plug & Play (easy to install, configure and use)
- Highly customizable
- Functions are bundled by [esbuild](https://github.com/evanw/esbuild)
- Offline server uses NodeJS `http` module
- Packaging is made by [node-archiver](https://github.com/archiverjs/node-archiver)
### Supported Runtimes
- NodeJS
- Python
- Ruby
### Minimum requirements
- Node v14.17.0+
- Serverless 2.0.0+
---
## Table of Contents
- [Installation](#installation)
- [Quick start](#quick-start)
- [Manual installation](#manual-installation)
- [Usage](#usage)
- [Invoke](#invoke)
- [Lifecycle](#lambda-execution-lifecycles)
- [Events](#events)
- [AWS Test button](#aws-console-test-button)
- [Function URL](#function-url)
- [Serverless Invoke local](#serverless-invoke-local)
- [AWS SDK Lambda Client](#aws-sdk)
- [Stream Response](#aws-lambda-response-stream)
- [Environment variables](#environment-variables)
- [Package](#package)
- [assets](#assets)
- [preserveDir](#preservedir)
- [files](#files)
- [Deploy](#deploy)
- [Advanced configuration](#advanced-configuration)
- [Plugins](#plugins)
- [Benchmarks](#benchmarks)
### **Installation**
#### **Quick start**
```bash
npx degit github:inqnuam/serverless-aws-lambda/templates/simple my-project
cd my-project && yarn install
yarn start
```
#### **Manual installation**
Usual node module installation...
```bash
yarn add -D serverless-aws-lambda
# or
npm install -D serverless-aws-lambda
```
Then add the plugin to your serverless plugins list
```yaml
service: myapp
frameworkVersion: "3"
configValidationMode: error
plugins:
- serverless-aws-lambda
```
---
### **Usage**
Start the local server
```bash
SLS_DEBUG="*" sls aws-lambda -s dev
```
During development the env variable `SLS_DEBUG="*"` is strongly recommanded as it will print a bunch of useful information.
It is also possible to set server port from the CLI with `--port` or `-p`.
This will overwrite serverless.yml custom > serverless-aws-lambda > port value if it is set.
For more options see [advanced configuration](#advanced-configuration).
---
### **Invoke**
#### **Lambda execution lifecycles.**
Succefull execution:

Failed execution:

#### **Events**
Offline server supports Application Load Balancer, API Gateway and Function URL endpoints out of box.
See [plugins](#plugins) for more triggers (SNS, SQS, etc.).
Appropriate `event` object is sent to the handler based on your lambda declaration.
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
events:
- alb:
listenerArn: arn:aws:elasticloadbalancing:eu-west-3:170838072631:listener/app/myAlb/bf88e6ec8f3d91df/e653b73728d04626
priority: 939
conditions:
path: "/paradise"
method: GET
```
All available local endpoints will be printed to the console when `SLS_DEBUG="*"` is set.
`myAwsomeLambda` is available at `http://localhost:PORT/paradise`
However if your declare both `alb` and `http` or `httpApi` inside a single lambda `events` with the same `path` you have to specify desired server by setting `alb` or `apg` inside your request's:
- header with `X-Mock-Type`.
- or in query string with `x_mock_type`.
Please note that invoking a lambda from sls CLI (`sls invoke local -f myFunction`) will not trigger the offline server. But will still make your handler ready to be invoked.
#### **AWS Console `Test` button**
To invoke your Lambda like with AWS Console's `Test` button, prefix your Lambda name by `@invoke/`.
Example:
```
http://localhost:3000/@invoke/myAwsomeLambda
```
#### **Function URL**
Function URL is available with `@url/` prefix. (must be enabled inside lambda declaration).
Example:
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
url: true
```
```
http://localhost:3000/@url/myAwsomeLambda
```
#### **Serverless Invoke local**
Works out of box.
[see options](https://www.serverless.com/framework/docs/providers/aws/cli-reference/invoke-local)
Example:
`serverless invoke local -f myAwsomeLambda`
#### **AWS SDK**
Invoking with `aws-sdk` Lambda Client requires to set client endpoint to local server host.
Example:
```js
const { LambdaClient, InvokeCommand } = require("@aws-sdk/client-lambda");
const client = new LambdaClient({ region: "us-east-1", endpoint: "http://localhost:3000" });
const DryRun = "DryRun";
const Event = "Event";
const RequestResponse = "RequestResponse";
const cmd = new InvokeCommand({
FunctionName: "myAwsomeLambda",
InvocationType: RequestResponse,
Payload: Buffer.from(JSON.stringify({ foo: "bar" })),
});
client
.send(cmd)
.then((data) => {
data.Payload = new TextDecoder("utf-8").decode(data.Payload);
console.log(data);
})
.catch((error) => {
// 🥲
console.log("error", error);
});
```
### **AWS Lambda Response Stream**
Stream responses are supported out of box through Function URL invoke or AWS SDK invoke.
See example:
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
url: # required only for Function URL invoke
invokeMode: RESPONSE_STREAM
```
```ts
// awsomeLambda.ts
import stream from "stream";
import { promisify } from "util";
const pipeline = promisify(stream.pipeline);
import { createReadStream } from "fs";
export const handler = awslambda.streamifyResponse(async (event, responseStream, context) => {
responseStream.setContentType("image/png");
// https://svs.gsfc.nasa.gov/vis/a030000/a030800/a030877/frames/5760x3240_16x9_01p/BlackMarble_2016_928m_europe_labeled.png
const streamImage = createReadStream("BlackMarble_2016_928m_europe_labeled.png");
await pipeline(streamImage, responseStream);
});
```
Example with AWS SDK:
```ts
import { LambdaClient, InvokeWithResponseStreamCommand } from "@aws-sdk/client-lambda";
const client = new LambdaClient({
region: "eu-west-3",
endpoint: "http://localhost:3000",
});
const cmd = new InvokeWithResponseStreamCommand({
FunctionName: "myAwsomeLambda",
InvocationType: "RequestResponse",
Payload: Buffer.from(JSON.stringify({ hello: "world" })),
ClientContext: Buffer.from(JSON.stringify({ anything: "some value" })).toString("base64"),
});
const data = await client.send(cmd);
for await (const x of data.EventStream) {
if (x.PayloadChunk) {
console.log(x.PayloadChunk.Payload);
}
}
```
---
### Environment variables
Lambdas are executed in worker threads. \*Only variables declared in your `serverless.yml` are injected into `process.env`.
\*In local mode following env variables are set for `sls invoke`, serverless-offline and AWS SAM compatibility.
- IS_LOCAL
- IS_OFFLINE
- AWS_SAM_LOCAL
If `NODE_ENV` is present it will be injected in both local mode, while deploying also during bundle process for optimized output.
---
### **Package**
serverless-aws-lambda bundles every (nodejs) handler separetly (with esbuild) and creates the artifact zip archive.
Archive will include bundeled handler and sourcemap (if enabled in esbuild).
#### - `assets`
By default bundle produced assets (css, png, svg etc.) are excluded.
To include all assets set `assets` to true.
For all functions set it at top-level `package`:
```yaml
package:
individually: true
assets: true # default false
functions:
myAwsomeLambda:
description: inherits assets from top-level package
handler: src/handlers/awsomeLambda.default
```
or by function:
```yaml
functions:
myAwsomeLambda:
package:
assets: false
description: don't includes assets
handler: src/handlers/awsomeLambda.default
```
include assets by file extension:
```yaml
functions:
myAwsomeLambda:
package:
assets: .css
description: include only css files
handler: src/handlers/awsomeLambda.default
```
```yaml
functions:
myAwsomeLambda:
package:
assets:
- .css
- .svg
description: include only css and svg files
handler: src/handlers/awsomeLambda.default
```
#### - `preserveDir` :
To preserve your project directories structure inside the archive set `preserveDir` globally or at function level.
```yaml
package:
individually: true
preserveDir: true # default true
functions:
myAwsomeLambda:
description: directories preserved
handler: src/handlers/awsomeLambda.default
dummyLambda:
package:
preserveDir: false
description: directories NOT preserved
handler: src/handlers/dummyLambda.default
```
#### - `files`
include additional files or directories into the package.
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
package:
files:
- ./resources/some/file.png
- ./resources/anotherFile.pdf
- ./images
```
By default `files` are inherited from top level `package`'s `files`.
This can be disabled with `inheritFiles` at function level.
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
package:
inheritFiles: false
files:
- ./resources/some/file.png
- ./node_modules/my-module
```
files may be added to the archive with custom path:
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
package:
files:
- { at: "./resources/some/file.png", as: "./documents/important.png" }
```
Adding files with a filter:
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
package:
files:
- { pattern: "./resources/some/*.png" }
```
If you need to preserve `pattern`'s directories structure inside the archive but search for files in another directory set `dir` value.
This will search for all .png files inside `./resources/images` but only `images` directory will be created inside the archive.
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
package:
files:
- { pattern: "images/*.png", dir: "./resources" }
```
Adding inline files:
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
package:
files:
- { text: "Hello world", dir: "./documents/hello.txt" }
```
---
### **Deploy**
Adding the param `online: false` will omit the deployement of your Lambda.
```yaml
functions:
myAwsomeLambda:
handler: src/handlers/awsomeLambda.default
online: false
```
To deploy a lambda by stage(s) set `online`'s value to target stage(s)
```yaml
functions:
lambdaOnlyInDev:
handler: src/handlers/awsomeLambda.default
online: dev
```
```yaml
functions:
lambdaOnlyInDevAndTest:
handler: src/handlers/awsomeLambda.default
online:
- dev
- test
```
### Extended properties
- `virtualEnvs`
a key-value object which will only be available inside [defineConfig](resources/defineConfig.md).
by default virtualEnvs are inherited from custom > virtualEnvs if exists.
---
### Advanced configuration:
To have more control over the plugin you can passe a config file via `configPath` param in plugin options:
```yaml
custom:
serverless-aws-lambda:
configPath: ./config.default
```
See [defineConfig](resources/defineConfig.md) for advanced configuration.
---
### Plugins:
- [AWS Local S3](resources/s3.md)
- [AWS Local SNS](resources/sns.md)
- [AWS Local SQS](resources/sqs.md)
- [DocumentDB Local Streams](https://github.com/Inqnuam/serverless-aws-lambda-documentdb-streams)
- [DynamoDB Local Streams](https://github.com/Inqnuam/serverless-aws-lambda-ddb-streams)
- [Jest](https://github.com/Inqnuam/serverless-aws-lambda-jest)
- [Vitest](https://github.com/Inqnuam/serverless-aws-lambda-vitest)
---
### **Benchmarks**
Hardware and software:
- iMac Pro 2017 (10 cors, 32Gb RAM)
- macOS Ventura (13.2.1)
- NodeJS v18.16.0
- Serverless 3.32.2
- serverless-aws-lambda 4.5.9
- serverless-offline 12.0.4
- serverless-esbuild 1.45.1
Handler:
```js
// src/handlers/visitor.js
let count = 0;
export const handler = async () => {
count++;
return {
statusCode: 200,
body: `Visit count ${count}`,
};
};
```
```yaml
functions:
visitor:
handler: src/handlers/visitor.handler
events:
- http: ANY /visitor
```
| 200 + 200 executions | Time (in seconds) | Memory used (mb) | CPU (core) usage | last invoke response | note |
| --------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------- | -------------------------- | -------------------- | ----------------------------------- |
| serverless-aws-lambda
cmd: `serverless aws-lambda` | sequential: 0.644
concurrent: 0.414 | idle: 125
peak: 169 | idle: 0,1%
peak: 15% | Visit count 400 | |
| serverless-offline + serverless-esbuild
cmd: `serverless offline --reloadHandler` | sequential: 10.4
concurrent: 2.8 | idle: 110
peak: 3960 | idle: 0,1%
peak: 537% | Visit count 1 | most of concurrent invocations fail |
---
### Use [Express](https://expressjs.com) syntax with your lambdas:
[See docs.](resources/express.md)