0.1.2 • Published 10 months ago

@mikeycoxon/aws-lambda-event-http-router v0.1.2

Weekly downloads
-
License
ISC
Repository
gitlab
Last release
10 months ago

Aws Lambda Event Http Router

The Aws Lambda Event Http Router is a helper, providing some convenience methods for routing REST based requests within an AWS Lambda's handler mainline function.

In other words, this package is useful...

export const handler: Handler = async (event: APIGatewayProxyEvent) => {
  // ... in here.
};

and in any delegates that your using.

Installation

The @mikeycoxon/aws-lambda-event-http-router is available as an npm package.

npm i @mikeycoxon/aws-lambda-event-http-router

Usage

The best way to get what this package does is by looking at an example Lambda handler:

// imports for all the delegates that you'd probably be using
import {
  handleGetExhibitRequest,
  handleListExhibitsRequest,
  handleCreateExhibitRequest,
  handleUpdateExhibitRequest,
  handleDeleteExhibitRequest,
  handleSendEmailRequest,
  handleFileUploadRequest,
  handleCreateFaqRequest,
  handleDeleteFaqRequest,
  handleGetFaqRequest,
  handleListFaqsRequest,
  handleUpdateFaqRequest,
  captureOrder,
  createOrder,
} from './variousHandlers';

The following lines show the main imports that you need. This first one is needed if you're using Typescript, its actually @types/aws-lambda if you're using javascript and you need to know how the APIGatewayProxyEvent is structured, have a look at the source code under fixtures for some example events.

import { APIGatewayProxyEvent } from 'aws-lambda';
import {
  AWSEventMap, DELETE, FILE, GET, PATCH, POST, ROUTE_KEY,
  stripLeadingSlash, UUID, withEvent, NotFoundError } from '@mikeycoxon/aws-lambda-event-http-router';

export const handler: Handler = async (event: APIGatewayProxyEvent) => {

remember that this is how you need to log as what gets consoled ends up in the Cloudwatch logs

console.log(event);

to demonstrate how you'd implement your own ids: give your key a name, and supply a valid RegExp.

NB. the leading slash - you want your matches to be as close as possible and ids always follow a forward slash

NB 2. in the case of paypal orders the orderId is always at the end of the url, which is why the $ is there

but don't include that, if you think your id may be somewhere in the middle of a path.

const PAYPAL_ORDER_ID_REX_KEY = 'paypalOrderId';

paypal order ids are 17 characters long and are alphanumeric eg 5O190127TN364715T, 3C679366HH908993F

const PAYPAL_ORDER_ID_REX = new RegExp(/\/[0-9a-zA-Z]{17}$/i);

OK. this is the most important bit, so pay attention.

the withEvent(event) method sets up most of the request method and path checkers. It also provides two resource (id) checkers, one for UUID type ids and one for fileNames These are all demonstrated lower down in the code.

Your urls will, of course, need to have your own resources. So you need to name each of those in the .addResources(event, name) builders.

Because we're demonstrating a custom id resource, we see the andCustomIdFormat(event, rex, name) builder being appended.

const req: AWSEventMap = withEvent(event)
  .andResource(event, 'emails')
  .andResource(event, 'exhibits/tags')
  .andResource(event, 'exhibits')
  .andResource(event, 'faqs')
  .andResource(event, 'files')
  .andResource(event, 'orders')
  .andCustomIdFormat(event, PAYPAL_ORDER_ID_REX, PAYPAL_ORDER_ID_REX_KEY);

As I mentioned earlier, you probably want to hand off the actual request handling to delegates.

So how this is done is by selecting on the characteristics of the event (mostly the event.httpMethod and event.pathParameters?.proxy attributes)

Therefore in the first example if its a POST and has 'emails' on the path then we want those requests to head off to the handleSendEmailRequest(event) delegate

if (req.get(POST) && req.get('emails')) {
  console.log(`calling SendEmail based on: ${req.get(ROUTE_KEY)}`);
  return handleSendEmailRequest(event);
}

but the order of these selectors matters. Have a look at the next three selectors

if (req.get(GET) && req.get('exhibits') && req.get(UUID)) {
  console.log(`calling GetExhibit based on: ${req.get(ROUTE_KEY)}`);
  return handleGetExhibitRequest(stripLeadingSlash(req.get(UUID)));
}

if (req.get(GET) && req.get('exhibits/tags')) {
  console.log(`calling ListExhibits based on: ${req.get(ROUTE_KEY)}`);
  return handleListExhibitsRequest();
}

if (req.get(GET) && req.get('exhibits')) {
  console.log(`calling ListExhibits based on: ${req.get(ROUTE_KEY)}`);
  return handleListExhibitsRequest();
}

You want the more specific matcher first, if you don't, you'll find that the simpler GET exhibits will catch all the GET requests for 'exhibits' and the more specific GET exhibits / UUID will never be called at all.

The GET exhibits/tags matcher is less specific than GET exhibits / UUID, so you need it go after.

if (req.get(POST) && req.get('exhibits')) {
  console.log(`calling CreateExhibit based on: ${req.get(ROUTE_KEY)}`);
  return handleCreateExhibitRequest(event);
}

the imports above show you that the GET, POST, PUT, PATCH and DELETE methods are all supplied by this package the ROUTE_KEY is also provided as a convenience to see the parts of the even all of the selectors are looking at.

if (req.get(PATCH) && req.get('exhibits')) {
  console.log(`calling UpdateExhibit based on: ${req.get(ROUTE_KEY)}`);
  return handleUpdateExhibitRequest(event);
}

if (req.get(DELETE) && req.get('exhibits') && req.get(UUID)) {
  console.log(`calling DeleteExhibit based on: ${req.get(ROUTE_KEY)}`);
  return handleDeleteExhibitRequest(event, stripLeadingSlash(req.get(UUID)));
}

if (req.get(GET) && req.get('faqs') && req.get(UUID)) {
  console.log(`calling GetFaq based on: ${req.get(ROUTE_KEY)}`);
  return handleGetFaqRequest(stripLeadingSlash(req.get(UUID)));
}

if (req.get(GET) && req.get('faqs')) {
  console.log(`calling ListFaqs based on: ${req.get(ROUTE_KEY)}`);
  return handleListFaqsRequest();
}

if (req.get(POST) && req.get('faqs')) {
  console.log(`calling CreateFaq based on: ${req.get(ROUTE_KEY)}`);
  return handleCreateFaqRequest(event);
}

if (req.get(PATCH) && req.get('faqs')) {
  console.log(`calling UpdateFaq based on: ${req.get(ROUTE_KEY)}`);
  return handleUpdateFaqRequest(event);
}

if (req.get(DELETE) && req.get('faqs') && req.get(UUID)) {
  console.log(`calling DeleteFaq based on: ${req.get(ROUTE_KEY)}`);
  return handleDeleteFaqRequest(event, stripLeadingSlash(req.get(UUID)));
}

if (req.get(POST) && req.get('files') && req.get(FILE)) {
  console.log(`uploading the file ${req.get(FILE)}`);
  return handleFileUploadRequest(event, stripLeadingSlash(req.get(FILE)));
}

if (req.get(POST) && req.get('orders') && req.get(PAYPAL_ORDER_ID_REX_KEY)) {
  console.log(`calling CaptureOrder based on: ${req.get(ROUTE_KEY)}`);
  return captureOrder(stripLeadingSlash(req.get(PAYPAL_ORDER_ID_REX_KEY)));
}

if (req.get(POST) && req.get('orders')) {
  console.log(`calling CreateOrder based on: ${req.get(ROUTE_KEY)}`);
  return createOrder(event);
}

return NotFoundError.format(new NotFoundError(`No handler for routeKey ${req.get(ROUTE_KEY)}.`));

this fall through call shows the other part of this library in that it prodes useful returnable types for all the most common HTTP status codes.

this may seem like it's not a big deal, but when going back through APIGateway, everything is cleaned off to 200 OK, 404 Missing and 500 Error, with no messaging, so you need a means to return status in the body this part of the library provides this.

}

TODOs

probably a lot but I don't know what they are yet. But when I find em, i'll get right onto it, I promise.

0.1.2

10 months ago

0.1.1

10 months ago

0.1.0

10 months ago

0.0.8

10 months ago

0.0.7

10 months ago

0.0.6

10 months ago

0.0.5

10 months ago

0.0.4

10 months ago

0.0.3

10 months ago

0.0.2

10 months ago

0.0.1

10 months ago