Skip to content

Latest commit

 

History

History
260 lines (198 loc) · 9.97 KB

README.md

File metadata and controls

260 lines (198 loc) · 9.97 KB

AWS Lambda wrapper library

This documentation is for v1 of the library

  1. Overview
  2. Installation and setup
  3. Supported events
  4. Example projects

Feedback appreciated! If you have an idea for how this library can be improved please open an issue.

Overview

TL;DR

This library provides custom Lambda function wrappers which expose standard, abstracted functionality so that developers can focus on writing business logic instead of parsing event payloads and crafting response objects.

Rationale and motivation

AWS Lambda supports a wide variety of event triggers, each with unique payloads and expected responses. The Lambda execution environment, however, only provides the raw events and has no included mechanisms for simplifying response object creation. For example, API Gateway events include only the raw request body, leaving it up to developers to implement parsing themselves. Similarly, the developer is responsible for creating a response object which includes the correct HTTP Status Code and headers. Given the standard nature of these kinds of concerns, this library exposes helpful abstractions like parsed HTTP bodies based on content-type headers, and success response functions which apply correct status codes and headers before invoking the Lambda callback.

Installation and setup

Install and save the package to package.json as a dependency:

npm i --save @manwaring/lambda-wrapper

yarn add @manwaring/lambda-wrapper

Optional configuration

If you want the wrapper to log request and response messages (helpful for debugging set an environemnt variable for LAMBDA_WRAPPER_LOG=true.

If you want each invocation to be tagged with the AWS region, environment/, and Git revision simply set environment variables for each: REGION=us-east-1, STAGE=prod, REVISION=f4ba682 (see git-rev-sync and serverless-plugin-git-variables for libraries that can help you set git revision)

If you want to take advantage of either Epsagon or IOPipe for advanced serverless instrumentation (highly recommmended!) you'll need to create an account with them and follow their instructions for setting up your project. If this library detects either of those packages it will automatically apply standard tagging (Epsagon labels and IOPipe labels and metrics) to each invocation for easier logging, monitoring, and troubleshooting.

Supported events

All of the events bellow have a corresponding wrapper which provides a deconstructed method signature exposing parsed/unmarshalled request parameters and helper response methods.

  1. API Gateway with support for cors headers and 200, 302, 400, and 500 responses
  2. CloudFormation Custom Resource with support for CloudFormation successes and failures
  3. DynamoDB Stream with support for success and failure responses
  4. Lambda Authorizer with support for creating access policies for successfully authorized requests
  5. SNS with support for success and failure responses
  6. Generic event wrapper with support for success and failure responses

API Gateway

Sample implementation

import { api } from '@manwaring/lambda-wrapper';

export const handler = api(async ({ body, path, success, error }) => {
  try {
    const { pathParam1, pathParam2 } = path;
    const results = await doSomething(body, pathParam1, pathParam2);
    success(results);
  } catch (err) {
    error(err);
  }
});

Properties and methods available on wrapper signature

interface ApiSignature {
  event: APIGatewayEvent; // original event
  body: any; // JSON or form parsed body payload if exists (based on content-type headers), otherwise the raw body object
  path: { [name: string]: string }; // path param payload as key-value pairs
  query: { [name: string]: string }; // query param payload as key-value pairs
  headers: { [name: string]: string }; // headers param payload as key-value pairs
  testRequest: boolean; // indicates if this is a test request, based on presence of headers matching 'Test-Request' or process.env.TEST_REQUEST_HEADER
  auth: any; // auth context from custom authorizer
  success(payload?: any, replacer?: (this: any, key: string, value: any) => any): void; // returns 200 status with payload
  invalid(errors?: string[]): void; // returns 400 status with errors
  redirect(url: string): void; // returns 302 redirect with new url
  error(error?: any): void; // returns 500 status with error
}

*Note that each callback helper functions (success, invalid, redirect, error) includes CORS-enabling header information

CloudFormation Custom Resource

Sample implementation

import { cloudFormation } from '@manwaring/lambda-wrapper';

export const handler = cloudFormation(({ event, success, failure }) => {
  try {
    const { BucketName } = event.ResourceProperties;
    success();
  } catch (err) {
    failure(err);
  }
});

*Note that currently the method wrapped by cloudFormation cannot be async - for reasons that aren't entirely clear to me when the method is async the requests to update CloudFormation with the correct action status fail, leaving a stack in the 'pending' state

Properties and methods available on wrapper signature

interface CloudFormationSignature {
  event: CloudFormationCustomResourceEvent; // original event
  success(payload?: any): void; // sends CloudFormation success event
  failure(message?: any): void; // sends CloudFormation failure event
}

DynamoDB Stream

Sample implementation

import { dynamodbStream } from '@manwaring/lambda-wrapper';

export const handler = dynamodbStream(async ({ newVersions, success, error }) => {
  try {
    newVersions.forEach((version) => console.log(version));
    return success(newVersions);
  } catch (err) {
    return error(err);
  }
});

Properties and methods available on wrapper signature

interface DynamoDBStreamSignature {
  event: DynamoDBStreamEvent; // original event
  newVersions: any[]; // array of all unmarshalled javascript objects of new images
  oldVersions: any[]; // array of all unmarshalled javascript objects of old images
  versions: Version[]; // array of full version object (new image, old image, etc - see Version interface)
  success(message?: any): void; // invokes lambda callback with success
  error(error?: any): void; // invokes lambda callback with error
}

interface Version {
  newVersion: any; // unmarshalled javascript object of new image (if exists) or null
  oldVersion: any; // unmarshalled javascript object of old image (if exists) or null
  keys: any; // unmarshalled javascript object of keys (includes key values)
  tableName: string; // name of the table the object came from
  tableArn: string; // arn of the table the object came from
  eventName: string; // name of the event (INSERT || MODIFY || REMOVE)
}

Lambda Authorizer

Sample implementation

import { authorizer } from '@manwaring/lambda-wrapper';
const verifier = new Verifier(); // setup and configure JWT validation library

export const handler = authorizer(async ({ token, valid, invalid }) => {
  try {
    if (!token) {
      return invalid('Missing token');
    }
    const jwt = await verifier.verifyAccessToken(token);
    valid(jwt);
  } catch (err) {
    invalid(err);
  }
});

Properties and methods available on wrapper signature

interface AuthorizerSignature {
  event: CustomAuthorizerEvent; // original event
  token: string; // authorizer token from original event
  valid(jwt: any): void; // creates AWS policy to authenticate request, and adds auth context if available
  invalid(message?: string[]): void; // returns 401 unauthorized
  error(error?: any): void; // records error information and returns 401 unauthorized
}

SNS

Sample implementation

import { sns } from '@manwaring/lambda-wrapper';

export const handler = sns(async ({ message, success, error }) => {
  try {
    console.log(message);
    return success();
  } catch (err) {
    return error(err);
  }
});

Properties and methods available on wrapper signature

interface SnsSignature {
  event: SNSEvent; // original event
  message: any; // JSON-parsed message from event (or raw message if not JSON)
  success(payload?: any): void; // invokes lambda callback with success
  error(error?: any): void; // invokes lambda callback with error
}

Generic event

Sample implementation

import { wrapper } from '@manwaring/lambda-wrapper';

export const handler = wrapper(async ({ event, success, error }) => {
  try {
    const { value1, value2 } = event;
    const results = await doSomething(value1, value2);
    success(results);
  } catch (err) {
    error(err);
  }
});

Properties and methods available on wrapper signature

interface WrapperSignature {
  event: any; // original event
  success(payload?: any): void; // invokes lambda callback with success response
  error(error?: any): void; // invokes lambda callback with error response
}

Example projects

THere is one working example of how this package can be used in a simple 'hello world' serverless application:

  1. Using the Serverless Framework and TypeScript