Serverless AWS Lambda

Plug & Play AWS Lambda NodeJS development tool with local API Gateway and Application Load Balancer support.

GitHub
View on Github

Serverless AWS Lambda

Plug & Play AWS Lambda NodeJS development tool with local API Gateway and Application Load Balancer support.

# serverless-aws-lambda [![NPM](https://nodei.co/npm/serverless-aws-lambda.png?compact=true)](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: ![lambda success](https://github.com/Inqnuam/serverless-aws-lambda/blob/main/resources/invokeSuccess.png) Failed execution: ![lambda error](https://github.com/Inqnuam/serverless-aws-lambda/blob/main/resources/invokeError.png) #### **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)