enhanced-aws-lambda-router
v0.1.0
Published
AWS lambda router
Downloads
4
Maintainers
Readme
AWS Lambda Router
A small library for AWS Lambda providing routing for API Gateway, Proxy Integrations, SNS and S3 Events.
Features
- Easy Handling of ANY method in API Gateways
- Simplifies writing lambda handlers (in nodejs > 8)
- Lambda Proxy Resource support for AWS API Gateway
- Enable CORS for requests
- No external dependencies
- Currently there are four
processors
(callers for Lambda) implemented:- API Gateway ANY method (called proxyIntegration)
- SNS
- SQS
- S3
- Compatibility with Typescript >= 3.5
Installation
Install via npm
$ npm install aws-lambda-router
or yarn
$ yarn add aws-lambda-router
Getting Started
This is a simple example of aws-lambda-router
in conjunction with ANY method and the API Gateway proxy integration. The following code will respond with a message when executed using an AWS API Gateway with a GET
request on URL path <base-url-of-gateway>/gateway-mapping/article/123
. The corresponding AWS API Gateway Resource is /article/{articleId}
.
import * as router from 'aws-lambda-router'
export const handler = router.handler({
proxyIntegration: {
routes: [
{
// request-path-pattern with a path variable:
path: '/article/:id',
method: 'GET',
// we can use the path param 'id' in the action call:
action: (request, context) => {
return "You called me with: " + request.paths.id;
}
},
{
// request-path-pattern with a path variable in Open API style:
path: '/section/{id}',
method: 'GET',
// we can use the path param 'id' in the action call:
action: (request, context) => {
return "You called me with: " + request.paths.id;
}
}
]
}
})
Proxy path support (work in progress)
The proxy integration usually works using a path configured in the API gateway. For example: /article/{id}
.
If you use the WIP proxy path support, the complete path will be used to match a route config in proxyIntegration
. This can be used to build an Simple Proxy with API Gateway
Example:
- Resource in API Gateway : /{proxy+} see Proxy Integration with a Proxy Resource
- Method: ANY
With the lambda configuration shown below the following paths are matched:
- api-gateway-host/article/list
- api-gateway-host/api/json/v1/schema
const router = require('aws-lambda-router');
exports.handler = router.handler({
proxyIntegration: {
proxyPath: proxy,
routes: [
{
path: '/article/list',
method: 'GET',
action: (request, context) => {
return "You called me with: " + request.path;
}
},
{
path: '/api/json/v1/schema',
method: 'GET',
action: (request, context) => {
return "You called me with: " + request.path;
}
}
]
}
})
Typescript example:
import * as router from 'aws-lambda-router'
import { ProxyIntegrationEvent } from 'aws-lambda-router/lib/proxyIntegration'
export const handler = router.handler({
proxyIntegration: {
routes: [
{
path: '/saveExample',
method: 'POST',
// request.body needs type assertion, because it defaults to type unknown (user input should be checked):
action: (request, context) => {
const { text } = request.body as { text: string }
return `You called me with: ${text}`
}
},
{
path: '/saveExample2',
method: 'POST',
// it's also possible to set a type (no type check):
action: (request: ProxyIntegrationEvent<{ text: string }>, context) => {
return `You called me with: ${request.body.text}`
}
}
]
}
}
Enable CORS
To activate CORS on all http methods (OPTIONS requests are handled automatically) you only need to set the parameter cors
to true
on the proxyIntegration
rule.
See the following example:
import * as router from 'aws-lambda-router'
export const handler = router.handler({
// for handling an http-call from an AWS Apigateway proxyIntegration we provide the following config:
proxyIntegration: {
cors: true,
routes: [
{
path: '/graphql',
method: 'POST',
// provide a function to be called with the appropriate data
action: (request, context) => doAnything(request.body)
}
]
}
})
If CORS is activated, these default headers will be sent on every response:
"Access-Control-Allow-Origin" = "'*'"
"Access-Control-Allow-Methods" = "'GET,POST,PUT,DELETE,HEAD,PATCH'"
"Access-Control-Allow-Headers" = "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
Error mapping
import * as router from 'aws-lambda-router'
export const handler = router.handler({
// for handling an http-call from an AWS Apigateway proxyIntegration we provide the following config:
proxyIntegration: {
routes: [
{
path: '/graphql',
method: 'POST',
action: (request, context) => doThrowAnException(request.body)
}
],
debug: true,
errorMapping: {
'NotFound': 404,
'MyCustomError': 429,
'ServerError': 500
}
}
})
function doThrowAnException(body) {
throw {reason: 'MyCustomError', message: 'Throw an error for this example'}
}
With the key word errorMapping
shown in the example above you can custom mapping of thrown errors to http response code error.
The action can throw an object like
"throw {reason: 'NotFound', message: 'object id not found'}"
and the http response then contains the configured value as response code and the message as the body.
SNS to Lambda Integrations
SNS Event Structure: https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html
For handling calls in Lambdas initiated from AWS-SNS you can use the following code snippet:
import * as router from 'aws-lambda-router'
export const handler = router.handler({
sns: {
routes: [
{
// a regex to match the content of the SNS-Subject:
subject: /.*/,
// Attention: the message is JSON-stringified
action: (sns, context) => service.doSomething(JSON.parse(sns.Message))
}
]
}
})
SQS to Lambda Integrations
For handling calls in Lambdas initiated from AWS-SQS you can use the following code snippet:
import * as router from 'aws-lambda-router'
export const handler = router.handler({
sqs: {
routes: [
{
// match complete SQS ARN:
source: 'arn:aws:sqs:us-west-2:594035263019:aticle-import',
// Attention: the messages Array is JSON-stringified
action: (messages, context) => messages.forEach(message => console.log(JSON.parse(message)))
},
{
// a regex to match the source SQS ARN:
source: /.*notification/,
// Attention: the messages array is JSON-stringified
action: (messages, context) => service.doNotify(messages)
}
]
}
})
An SQS message always contains an array of records. In each SQS record there is the message in the body JSON key.
The action
method gets all body elements from the router as an array.
If more than one route matches, only the first is used!
S3 to Lambda Integrations
Lambdas can be triggered by S3 events. The router now supports these events. With the router it is very easy and flexible to connect a lambda to different s3 sources (different buckets). The following configurations are available:
- bucketName: By specifying a fixed bucketName all s3 records with this bucket name are forwarded to a certain action. Instead of a fixed bucket a RegExp is also possible.
- eventName: By configuring the S3 event name the routing can be further restricted. A RegExp is also possible here.
- objectKeyPrefix: fixed string as an prefix of an object key (but not an RegExp). Is useful if you want to organize your bucket in subfolder.
A combination of bucketName, eventName and objectKeyPrefix is possible. If no bucketName, eventName and objectKeyPrefix is configured, all records of s3 events are forwarded to the action.
The action method will be called with the records of the S3Event Structure
The following examples demonstrates the most use cases:
import * as router from 'aws-lambda-router'
export const handler = router.handler({
s3: {
routes: [
{
//match every s3 record to this action
action: (record, context) => console.log(record.s3.object.key, record.eventName)
},
{
//match s3 events which created, bucket name is whitelisted here
eventName: 'ObjectCreated:Put',
action: (record, context) => console.log(record.s3.object.key, record.eventName)
},
{
//event name is an regex: match 'ObjectCreated:Put' or 'ObjectCreated:Copy'
eventName: /ObjectCreated:*/,
action: (record, context) => console.log(record.s3.object.key, record.eventName)
},
{
//exact name of bucket 'myBucket', event name is whitelisted and will not be checked
bucketName: 'myBucket',
action: (record, context) => console.log(record.s3.object.key, record.eventName)
},
{
//regex of bucket name (all buckets started with 'bucket-dev-' will be machted
bucketName: /^bucket-dev-.*/,
action: (record, context) => console.log(record.s3.object.key, record.eventName)
},
{
//action only will be called if bucket and event matched to the given regex
bucketName: /bucket-dev-.*/,
eventName: /ObjectCreated:*/,
action: (record, context) => console.log(event.s3.object.key, record.eventName)
},
{
//action only will be called if bucket and event matched to the given fixed string
bucketName: 'bucket',
eventName: 'ObjectRemoved:Delete',
action: (record, context) => console.log(event.s3.object.key, record.eventName)
},
{
//match if s3 events comes from Bucket 'bucket' with event 'ObjectRemoved:Delete'
// and the object key starts with /upload
objectKeyPrefix: '/upload',
bucketName: 'bucket',
eventName: 'ObjectRemoved:Delete',
action: (record, context) => console.log(record.s3.object.key, record.eventName)
}
],
debug: true
}
})
Per s3 event there can be several records per event. The action methods are called one after the other record. The result of the action method is an array with objects insides.
Custom response
Per default a status code 200 will be returned. This behavior can be overridden.
By providing a body property in the returned object you can modify the status code and response headers.
return {
// Allow for custom status codes depending on execution.
statusCode: 218,
// Headers will merge with CORS headers when enabled.
// Will merge with Content-Type: application/json
headers: {
'x-new-header': 'another-value'
},
// When returning a custom response object, a key of body is required
// The value of body needs to be JSON stringified, this matches
// the expected response for an AWS Lambda.
body: JSON.stringify({
foo: 'bar'
})
}
Local developement
The best is to work with yarn link
See here: https://yarnpkg.com/en/docs/cli/link
Releasing
It's simple.
Increase version in package.json (using semantic version syntax). After than create an new tag in github (with description, can be the same as of the release history below) with the same version (like v0.98.9). Our build pipeline at Travis CI will be started and release an new version at NPM Repository.
Thats all.
Release History
- 0.8.2
- added support for Open API parameter definitions e.g.: /section/{id}
- 0.8.1
- fix: changed ProxyIntegrationEvent body type to be generic but defaults to unknown
- fix: changed @types/aws-lambda from devDependency to dependency
- breaking: error response objects (thrown or rejected) now need to set
statusCode
instead ofstatus
(consistent with response)
- 0.7.1
- code style cleanup
- fix: hosted package on npmjs should now worked
- 0.7.0
- migrate to typescript
- using @types/aws-lambda typings
- proxyIntegration: cors is now optional (default: false)
- removed use of aws lambda handler callback function (using Promise instead)
- experimental proxy path support (thanks to @swaner)
- 0.6.2
- take away old gulp dependency to run tests, works now with scripts in package.json
- normalize request path to start from local host (thanks to @napicella)
- 0.6.1
- s3: fix: aggregate result promises to one promise;
- s3: s3Route interface
- 0.6.0
- new feature: S3 routes available.
- 0.5.0
- new feature: SQS route integration now available;
- bugfix: SNS integration now works with Array of message instead of single message
- 0.4.0
- now the Context Object pass through
- 0.3.1 proxyIntegration:
- avoid error if response object is not set;
- add some debug logging
- 0.3.0
- proxyIntegration: add PATCH method;
- allow for custom status codes from route (thanks to @mintuz)
- 0.2.2
- proxyIntegration: set correct header values now for CORS
- 0.2.1
- proxyIntegration: CORS in Preflight, status code 400 for invalid body, set more CORS headers as default
- 0.2.0 Attention: breaking changes for configuration;
- add SNS event process
- 0.1.0
- make it work now
- 0.0.1
- initial release