@mikeycoxon/aws-lambda-event-http-router v0.1.2
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.