UNPKG

12 kBMarkdownView Raw
1<p align="center">
2 <img height="140" src="https://avatars0.githubusercontent.com/u/36457275?s=400&u=16d355f384ed7f8e0655b7ed1d70ff2e411690d8&v=4e">
3 <img height="140" src="https://user-images.githubusercontent.com/2955468/44874383-0168f780-ac69-11e8-8e51-774678cbd966.png">
4</p>
5
6<p align="center">
7 <a href="https://npmjs.com/package/@manwaring/lambda-wrapper">
8 <img src="https://flat.badgen.net/npm/v/@manwaring/lambda-wrapper?icon=npm&label=npm@latest"></a>
9 <a href="https://www.npmjs.com/package/@manwaring/lambda-wrapper">
10 <img src="https://flat.badgen.net/npm/dt/@manwaring/lambda-wrapper?icon=npm"></a>
11 <a href="https://codecov.io/gh/manwaring/lambda-wrapper">
12 <img src="https://flat.badgen.net/codecov/c/github/manwaring/lambda-wrapper/?icon=codecov"></a>
13 <a href="https://packagephobia.now.sh/result?p=@manwaring/lambda-wrapper">
14 <img src="https://flat.badgen.net/packagephobia/install/@manwaring/lambda-wrapper"></a>
15 <a href="https://www.npmjs.com/package/@manwaring/lambda-wrapper">
16 <img src="https://flat.badgen.net/npm/license/@manwaring/lambda-wrapper"></a>
17</p>
18
19<p align="center">
20 <a href="https://circleci.com/gh/manwaring/lambda-wrapper">
21 <img src="https://flat.badgen.net/circleci/github/manwaring/lambda-wrapper/master?icon=circleci"></a>
22 <a href="https://flat.badgen.net/dependabot/manwaring/lambda-wrapper">
23 <img src="https://flat.badgen.net/dependabot/manwaring/lambda-wrapper/?icon=dependabot&label=dependabot"></a>
24 <a href="https://david-dm.org/manwaring/lambda-wrapper">
25 <img src="https://flat.badgen.net/david/dep/manwaring/lambda-wrapper"></a>
26 <a href="https://david-dm.org/manwaring/lambda-wrapper?type=dev">
27 <img src="https://flat.badgen.net/david/dev/manwaring/lambda-wrapper/?label=dev+dependencies"></a>
28</p>
29
30# AWS Lambda wrapper library
31
32### This documentation is for v3 of the library - [go here for v1](old-docs/v1/README.md) and [here for v2](old-docs/v2/README.md)
33
341. [Overview](#overview)
351. [Installation and setup](#installation-and-setup)
36 - [Optional configuration](#optional-configuration)
371. [Supported events](#supported-events)
38 - [API Gateway](#api-gateway)
39 - [CloudFormation Custom Resource](#cloudformation-custom-resource)
40 - [DynamoDB Stream](#dynamodb-stream)
41 - [Lambda Authorizer](#lambda-authorizer)
42 - [SNS](#sns)
43 - [Generic event](#generic-event)
441. [Example projects](#example-projects)
45
46_Feedback appreciated! If you have an idea for how this library can be improved [please open an issue](https://github.com/manwaring/lambda-wrapper/issues/new)._
47
48# Overview
49
50### TL;DR
51
52This 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.
53
54### Rationale and motivation
55
56AWS 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 create response objects with the correct status codes and headers for returning.
57
58# Installation and setup
59
60Install and save the package to `package.json` as a dependency:
61
62`npm i --save @manwaring/lambda-wrapper`
63
64`yarn add @manwaring/lambda-wrapper`
65
66## Optional configuration
67
68If you want the wrapper to log request and response messages (helpful for debugging set an environemnt variable for `LAMBDA_WRAPPER_LOG=true`.
69
70If 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](https://www.npmjs.com/package/git-rev-sync) and [serverless-plugin-git-variables](https://www.npmjs.com/package/serverless-plugin-git-variables) for libraries that can help you set git revision)
71
72# Supported events
73
74All of the events bellow have a corresponding wrapper which provides a deconstructed method signature exposing parsed/unmarshalled request parameters and helper response methods.
75
761. [API Gateway](#api-gateway) with support for cors headers and 200, 302, 400, and 500 responses
771. [CloudFormation Custom Resource](#cloudformation-custom-resource) with support for CloudFormation successes and failures
781. [DynamoDB Stream](#dynamodb-stream) with support for success and failure responses
791. [Lambda Authorizer](#lambda-authorizer) with support for creating access policies for successfully authorized requests
801. [SNS](#sns) with support for success and failure responses
811. [Generic event](#generic-event) wrapper with support for success and failure responses
82
83## API Gateway
84
85### Sample implementation
86
87```ts
88import { api } from '@manwaring/lambda-wrapper';
89
90export const handler = api(async ({ body, path, success, error }) => {
91 try {
92 const { pathParam1, pathParam2 } = path;
93 const results = await doSomething(body, pathParam1, pathParam2);
94 return success(results);
95 } catch (err) {
96 return error(err);
97 }
98});
99```
100
101### Properties and methods available on wrapper signature
102
103```ts
104export interface ApiSignature {
105 event: APIGatewayEvent; // original event
106 body: any; // JSON parsed body payload if exists (otherwise null)
107 path: { [name: string]: string }; // path param payload as key-value pairs if exists (otherwise null)
108 query: { [name: string]: string }; // query param payload as key-value pairs if exists (otherwise null)
109 headers: { [name: string]: string }; // header payload as key-value pairs if exists (otherwise null)
110 testRequest: boolean; // indicates if this is a test request - looks for a header matching process.env.TEST_REQUEST_HEADER (dynamic from application) or 'Test-Request' (default)
111 auth: any; // auth context from custom authorizer if exists (otherwise null)
112 success(payload?: any, replacer?: (this: any, key: string, value: any) => any): ApiResponse; // returns 200 status code with optional payload as body
113 invalid(errors?: string[]): ApiResponse; // returns 400 status code with optional errors as body
114 notFound(message?: string): ApiResponse; // returns 404 status code with optional message as body
115 notAuthorized(message?: string): ApiResponse; // returns 403 status code with optional message as body
116 redirect(url: string): ApiResponse; // returns 302 status code (redirect) with new url
117 error(error?: any): ApiResponse; // returns 500 status code with optional error as body
118}
119
120interface ApiResponse {
121 statusCode: number;
122 headers: { [name: string]: string | boolean };
123 body?: string;
124}
125```
126
127\*Note that each callback helper functions (success, invalid, redirect, error) includes CORS-enabling header information
128
129## CloudFormation Custom Resource
130
131### Sample implementation
132
133```ts
134import { cloudFormation } from '@manwaring/lambda-wrapper';
135
136export const handler = cloudFormation(({ event, success, failure }) => {
137 try {
138 const { BucketName } = event.ResourceProperties;
139 return success();
140 } catch (err) {
141 return failure(err);
142 }
143});
144```
145
146\*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
147
148### Properties and methods available on wrapper signature
149
150```ts
151interface CloudFormationSignature {
152 event: CloudFormationCustomResourceEvent; // original event
153 success(payload?: any): void; // sends CloudFormation success event
154 failure(message?: any): void; // sends CloudFormation failure event
155}
156```
157
158## DynamoDB Stream
159
160### Sample implementation
161
162```ts
163import { dynamodbStream } from '@manwaring/lambda-wrapper';
164
165export const handler = dynamodbStream(async ({ newVersions, success, error }) => {
166 try {
167 newVersions.forEach(version => console.log(version));
168 return success(newVersions);
169 } catch (err) {
170 return error(err);
171 }
172});
173```
174
175### Properties and methods available on wrapper signature
176
177```ts
178interface DynamoDBStreamSignature {
179 event: DynamoDBStreamEvent; // original event
180 newVersions: any[]; // array of all unmarshalled javascript objects of new images
181 oldVersions: any[]; // array of all unmarshalled javascript objects of old images
182 versions: Version[]; // array of full version object (new image, old image, etc - see Version interface)
183 success(message?: any): any; // logs and returns the message
184 error(error?: any): void; // logs the error and throws it
185}
186
187interface Version {
188 newVersion: any; // unmarshalled javascript object of new image (if exists) or null
189 oldVersion: any; // unmarshalled javascript object of old image (if exists) or null
190 keys: any; // unmarshalled javascript object of keys (includes key values)
191 tableName: string; // name of the table the object came from
192 tableArn: string; // arn of the table the object came from
193 eventName: string; // name of the event (INSERT || MODIFY || REMOVE)
194}
195```
196
197## Lambda Authorizer
198
199### Sample implementation
200
201```ts
202import { authorizer } from '@manwaring/lambda-wrapper';
203const verifier = new Verifier(); // setup and configure JWT validation library
204
205export const handler = authorizer(async ({ token, valid, invalid }) => {
206 try {
207 if (!token) {
208 return invalid('Missing token');
209 }
210 const jwt = await verifier.verifyAccessToken(token);
211 return valid(jwt);
212 } catch (err) {
213 return invalid(err);
214 }
215});
216```
217
218### Properties and methods available on wrapper signature
219
220```ts
221interface AuthorizerSignature {
222 event: CustomAuthorizerEvent; // original event
223 token: string; // authorizer token from original event
224 valid(jwt: any): Policy; // returns AWS policy to authenticate request, and adds auth context if available
225 invalid(message?: any): void; // records invalid information and throws 401 unauthorized
226 error(error?: any): void; // records error information and throws 401 unauthorized
227}
228
229interface Policy {
230 principalId: string;
231 policyDocument: {
232 Version: string;
233 Statement: {
234 Action: string;
235 Effect: string;
236 Resource: string;
237 }[];
238 };
239}
240```
241
242## SNS
243
244### Sample implementation
245
246```ts
247import { sns } from '@manwaring/lambda-wrapper';
248
249export const handler = sns(async ({ message, success, error }) => {
250 try {
251 console.log(message);
252 return success();
253 } catch (err) {
254 return error(err);
255 }
256});
257```
258
259### Properties and methods available on wrapper signature
260
261```ts
262interface SnsSignature {
263 event: SNSEvent; // original event
264 message: any; // JSON-parsed message from event
265 success(message?: any): any; // logs and returns the message
266 error(error?: any): void; // logs the error and throws
267}
268```
269
270## Generic event
271
272### Sample implementation
273
274```ts
275import { wrapper } from '@manwaring/lambda-wrapper';
276
277export const handler = wrapper(async ({ event, success, error }) => {
278 try {
279 const { value1, value2 } = event;
280 const results = await doSomething(value1, value2);
281 return success(results);
282 } catch (err) {
283 return error(err);
284 }
285});
286```
287
288### Properties and methods available on wrapper signature
289
290```ts
291interface WrapperSignature {
292 event: any; // original event
293 success(message?: any): any; // logs and returns the message
294 error(error?: any): void; // logs the error and throws
295}
296```
297
298# Example projects
299
300There is one [working example](examples) of how this package can be used in a simple 'hello world' serverless application:
301
3021. [Using the Serverless Framework and TypeScript](examples/ts)