Giter Site home page Giter Site logo

spring-media / aws-lambda-router Goto Github PK

View Code? Open in Web Editor NEW
98.0 45.0 52.0 376 KB

Improved routing for AWS Lambda like SNS and ApiGateway

License: Apache License 2.0

JavaScript 0.08% TypeScript 99.92%
sns aws aws-lambda api-gateway router cors aws-lambda-router

aws-lambda-router's Introduction

AWS Lambda Router Build Status

codecov npm version dependencies

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 - well, almost, only types of aws-lambda :-)
  • 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: {
        // assumes the first path part is `stage` and removes it.
        removeBasePath: true
        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:

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: {
        removeBasePath: true,
        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}`
                }
            }
        ]
    }
}

When an API Mapping is configured for a custom domain

If your API-gateway custom domain has a mapping for stage you will need to set removeBasePath to false in order to allow the route to be properly matched on.

const router = require('aws-lambda-router');

exports.handler = router.handler({
    proxyIntegration: {
        removeBasePath: false,
        routes: [
            {
                path: '/api/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;
                }
            }
        ]
    }
})

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:

Default CORS 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'"

Customizing CORS

To customize CORS for all routes pass any of the following options to the proxyIntegration cors property. If a property is not set then it will default to the above default CORS headers.

  • origin: Configures the Access-Control-Allow-Origin CORS header. Possible values:
    • Boolean - set origin to true to reflect the request origin or set it to false to disable CORS.
    • String - set origin to a specific origin. For example if you set it to "http://example.com" only requests from "http://example.com" will be allowed.
    • RegExp - set origin to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern /example\.com$/ will reflect any request that is coming from an origin ending with "example.com".
    • Array - set origin to an array of valid origins. Each origin can be a String or a RegExp. For example ["http://example1.com", /\.example2\.com$/] will accept any request from "http://example1.com" or from a subdomain of "example2.com".
    • Function - set origin to a function to be evaluated. The function will get passed the APIGatewayProxyEvent and must return the allowed origin or false
  • methods: Configures the Access-Control-Allow-Methods CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: ['GET', 'PUT', 'POST']).
  • allowedHeaders: Configures the Access-Control-Allow-Headers CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: ['Content-Type', 'Authorization']). If not specified, defaults to reflecting the headers specified in the request's Access-Control-Request-Headers header.
  • exposedHeaders: Configures the Access-Control-Expose-Headers CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: ['Content-Range', 'X-Content-Range']). If not specified, no custom headers are exposed.
  • credentials: Configures the Access-Control-Allow-Credentials CORS header. Set to true to pass the header, otherwise it is omitted.
  • maxAge: Configures the Access-Control-Max-Age CORS header. Set to an integer to pass the header, otherwise it is omitted.
Customize CORS 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: {
          origin: 'https://test.example.com', // Only allow CORS request from this url
          methods: ['GET', 'POST', 'PUT']     // Only allow these HTTP methods to make requests
        },
        routes: [
            {
                path: '/graphql',
                method: 'POST',
                // provide a function to be called with the appropriate data
                action: (request, context) => doAnything(request.body)
            }
        ]
    }
})

Error mapping / handling

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 adjust the assignment 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.

Genereric error handler for proxyIntegration

Also there is implemented an generic error handler. The idea is to have a place for handling error logging and also return custom error messages.

Generic error handling example
onError: (error, event, context) => {
    // Global exceptions goes here, works for sns, s3 and sqs should end up here aswell
    console.log(error)
},
proxyIntegration: {
    onError: (error, request, context) => {
        // proxy integration exceptions goes here
        console.log('Error', error, request, context)
    },
    routes: ...
}

// Also support returning a response:
proxyIntegration: {
    onError: async (error) => {
        console.log('Error', error)
        await someAsyncMethod();
        return {
           statusCode: 401,
           body: Not allowed
        }
    },
    routes: ...
}

For more examples please look into the tests of proxyIntegration.

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:

SNS to Lambda example
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, records) => service.doSomething(JSON.parse(sns.Message))
            }
        ]
    }
})

The records parameter contains SNSEventRecord[]. An exampe event structure can be found here. For example you can parse now the message attributes of the SNS or reads the topic arn of SNS.

SQS to Lambda Integrations

For handling calls in Lambdas initiated from AWS-SQS you can use the following code snippet:

SQS to Lambda example
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, records) => 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, records) => 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!

The records parameter contains the complete array of records, which handled by aws-lambda-router. An exampe can be found here. This gives you the possibility to read metadata from the event. For example, you can parse the message attributes of the SQS and use them for further processing.

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:

S3 to Lambda example
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.

Response example
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

see CHANGELOG.md

aws-lambda-router's People

Contributors

chgohlke avatar dependabot[bot] avatar ethandavis avatar evgenykireev avatar gerjunior avatar jguice avatar jhpg avatar jpavek avatar jzlai avatar lukasbombach avatar mintuz avatar napicell avatar snorberhuis avatar swaner avatar syrok avatar tdt17 avatar terrymooreii avatar ujann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aws-lambda-router's Issues

Pass rawBody instead of parsed

I customized this module locally and added a jsonParse flag to the routes so that I can have the option to parse or not parse a JSON event body. Since I don't know typescript I can't do a pull request on your code but this is a very quick to add and useful feature.

example route:

routes: [
      {
        path: '/somepath',
        method: 'POST',
        action: async (event) => {
         //some function
          };
          return response;
        },
       jsonParse: true
      }
]

code change (javascript proxyIntegration.js line 80):

    if (event.body && actionConfig.jsonParse) {
      try {
        proxyEvent.body = JSON.parse(event.body);
      } catch (parseError) {
        console.log(`Could not parse body as json: ${event.body}`, parseError);
        return {
          statusCode: 400,
          headers,
          body: JSON.stringify({ message: 'body is not a valid JSON', error: 'ParseError' }),
        };
      }
    } else proxyEvent.body = event.body;

Release 0.9.1?

Any change you can release the latest code merges with a new npm version? #53

No Custom Uncaught Error Handling

In trying to implement aws-lambda-router alongside honeybadger, I realized that there's no real way to catch uncaught errors as everything is caught. It would be nice to have an option to allow for uncaught errors, so that things like Honeybadger can report on unexpected behavior.

An additional solution may include allowing users to define their own function that get run in a catch.

add new maintainer?

Hi folks - I see this is a bit stale and there is at least one PR pending that seems to have fixed issues for me. Volunteering to maintain.

ProxyIntegrationRoute.action() has incorrect type signature

Hi, request.body is typed as string | null. This is incorrect as request.body is being JSON-decoded.

The workaround:

const body =
  req.body === null
    ? undefined
    : ((req.body as unknown) as CreateUserRequest);

Is undesirable. Perhaps updating the typing for ProxyIntegrationParams is necessary, adding body: object?

ProxyIntegration/Typescript: Not returning ProxyIntegrationResult

Hi

Updated aws-lambda-router (from 0.6.2) on our project using Typescript, and noticed strange issue.

Aws-lambda-router prevents returning non-string responses from proxyIntegration actions here: https://github.com/spring-media/aws-lambda-router/blob/master/lib/proxyIntegration.ts#L22

So our old code, which returns plain JS objects, does not work anymore, as it fails Typescript checks.

However, if I JSON.stringify my response object, before returning it from action, aws-lambda-router wraps it with another JSON.stringify call here: https://github.com/spring-media/aws-lambda-router/blob/master/lib/proxyIntegration.ts#L61 (because of condition in line 57 does not handle plain string responses).

If I do the old way, and return plain JS object from my action (and cast it to string to make typescript happy) everything seems to work as it should.

So, is something wrong with proxyIntegration Action types? Or should I be returning instances of ProxyIntegrationResult from my actions nowadays?

EDIT:

Our old code (for aws-lambda-router 0.6.2) looked like this:

import * as router from 'aws-lambda-router';

export const handler = router.handler({
  proxyIntegration: {
    cors: false,
    routes: [

      {
        path: '/foobar',
        method: 'GET',
        action: () => ({ isFoobar: true }),
      },
    ],
  },
});

With new aws-lambda-router (0.9.1), this fails with Typescript (4.1.3) error:

foo.ts:11:23 - error TS2322: Type '{ isFoobar: boolean; }' is not assignable to type 'string | Promise<string> | ProxyIntegrationResult | Promise<ProxyIntegrationResult>'.
  Type '{ isFoobar: boolean; }' is missing the following properties from type 'Promise<ProxyIntegrationResult>': then, catch, [Symbol.toStringTag], finally

11         action: () => ({ isFoobar: true }),
                         ~~~~~~~~~~~~~~~~~~~

  ../../node_modules/aws-lambda-router/lib/proxyIntegration.d.ts:20:13
    20     action: (request: ProxyIntegrationEvent<unknown>, context: APIGatewayEventRequestContext) => ProxyIntegrationResult | Promise<ProxyIntegrationResult> | string | Promise<string>;
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    The expected type comes from the return type of this signature.


Found 1 error.

If I change it to like this:

import * as router from 'aws-lambda-router';

export const handler = router.handler({
  proxyIntegration: {
    cors: false,
    routes: [

      {
        path: '/foobar',
        method: 'GET',
        action: () => {
          return JSON.stringify({ isFoobar: true });
        },
      },
    ],
  },
});

It passes typescript compilation, but calls JSON.stringify second time for that string, adding second set of quotes.

I can make it work like this:

import * as router from 'aws-lambda-router';

export const handler = router.handler({
  proxyIntegration: {
    cors: false,
    routes: [

      {
        path: '/foobar',
        method: 'GET',
        action: () => {
          return { isFoobar: true } as unknown as string;
        },
      },
    ],
  },
});

But that kind of kills the point of using typescript.

Custom CORS Headers

We have a need to be able to modify the default CORS headers to support specific domains for the allowed origin. We'd prefer to avoid having to add these headers in each handler or as middleware, if possible.

A possible solution:
Allow for a handler (anonymous function or arrow function) to be assigned to the cors variable or another variable in the proxy configuration. That handler would be responsible for setting the header values as shown here or return an object with the key values that could be merged with the other headers.

This would allow for the handy option, to handle all OPTIONS requests, to stay in place but provide a greater level of customization for each use-case. The default behavior could be to use the existing function that sets the headers if no implementation is provided.

Using webpack we get warnings in typescript file

First of all, thank you for the library.

However, seeing warnings like this when compiling with webpack. Any ideas on how to fix?

WARNING in ./node_modules/aws-lambda-router/lib/proxyIntegration.d.ts 3:8
Module parse failed: Unexpected token (3:8)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import { APIGatewayEventRequestContext, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
| import { ProcessMethod } from './EventProcessor';
> declare type ProxyIntegrationParams = {
|     paths?: {
|         [paramId: string]: string;
 @ ./node_modules/aws-lambda-router/lib sync ^\.\/.*$ ./proxyIntegration.d.ts
 @ ./node_modules/aws-lambda-router/index.js
 @ ./src/lambdas/es_manager.ts

Webpack config is like this:

const path = require('path');
const fs = require('fs');
const nodeBuiltins = require('builtin-modules');

const lambdaDir = path.join('.', 'src', 'lambdas');
const lambdaNames = fs.readdirSync(path.join(__dirname, lambdaDir));

const DIST_DIR = path.join(__dirname, 'dist');

const entry = lambdaNames
  .reduce((entryMap, lambdaName) => {
    entryMap[lambdaName.replace('.ts', '')] = path.join(__dirname, lambdaDir, `${lambdaName}`);
    return entryMap;
  }, {});

const externals = ['aws-sdk']
  .concat(nodeBuiltins)
  .reduce((externalsMap, moduleName) => {
    externalsMap[moduleName] = moduleName;
    return externalsMap;
  }, {});

module.exports = {
    entry: entry,
    externals: externals,
    module: {
      rules: [
        {
          test: /\.tsx?$/,
          use: 'ts-loader',
          exclude: /node_modules/
        }
      ]
    },
    resolve: {
      extensions: [ '.tsx', '.ts', '.js' ]
    },
    output: {
      libraryTarget: 'commonjs',
      filename: '[name].js',
      path: path.resolve(__dirname, 'dist')
    },
    optimization:{
      minimize: false, // <---- disables uglify.
      // minimizer: [new UglifyJsPlugin()] if you want to customize it.
    },
    target: 'node',
    mode: 'production'
};

package.json is this

{
  "name": "test",
  "version": "1.0.0",
  "description": "test-test",
  "main": "index.js",
  "scripts": {
    "compile": "rm -rf ./dist && tsc",
    "package": "rm -rf ./dist && webpack",
    "build": "npm run package && rm -f ./main.zip && cd ./dist && zip -r ../main .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "aws-lambda-router": "^0.8.3"
  },
  "devDependencies": {
    "@types/aws-lambda": "^8.10.57",
    "@types/node": "^14.0.14",
    "aws-sdk": "^2.705.0",
    "builtin-modules": "^3.1.0",
    "ts-loader": "^7.0.5",
    "typescript": "^3.9.5",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.12"
  }
}

Doesn't work with raw text bodies

I setup a POST route with a handler and the handler never got invoked despite debug mode logging that it found a matching route. The logs just end there.

I was able to eventually reproduce the issue and the response was Unexpected 'b'.

I'm working on a PR to handle text types and add debug logging when the mime type isn't valid but I wanted to bring this up. I got around it by handing that specific route prior to calling the route handler like so:

module.exports.handler = async (event, context, callback) => {
  if (event.path === "/myPath" && event.httpMethod === "POST") {
    return controller.myPath(event, context, callback);
  } else {
    return router.handler(routeConfig)(event, context, callback);
  }
};

Multiple handlers within one event

We often need to do routing to an appropriate handler based on an attribute of a particular record within an event. For example, we have a generic SQS topic with a number of handlers based on an attribute of SQL message.
You have a similar concept for HTTP Proxy integration when you match a request based on the path.

Are there any plans to support this in this library?

The way I'd imagine it could work is to add records attribute to multi-record event like sqs. This attribute defines a match and handler functions. For example,

export const handler = router.handler({
    sqs: {
        routes: [
            {
                source: /.*notification/,
                records: {
                    match(record) => record.type === 'user',
                    handler(record) => service.doNotify(record)
                }
            }
        ]
    }
})

Happy to collaborate on this.

How can I create path vars that aren't separated with "/"

I have a route that looks something like this:

path: "thing/{var1}/{var2}/{var3}/{name}.{extension}"

But right now the code that extract path names and path values doesn't support this.

Now my regex foo is super lame. I was hoping someone could help me identify the fix for this. If it's something this lib doesnt want to support I can fork.

if it is, I dont mind submitted a PR with some guidance.

const extractPathValues = (pathExpression: string, httpPath: string) => {
  const pathExpressionPattern = pathExpression.replace(/{[\w]+}|:[\w]+/g, '([^/]+)')
  const pathValueRegex = new RegExp(`^${pathExpressionPattern}$`)
  const pathValues = pathValueRegex.exec(httpPath)
  return pathValues && pathValues.length > 0 ? pathValues.slice(1) : null
}

const extractPathNames = (pathExpression: string) => {
  const pathExpressionPattern = pathExpression.replace(/{[\w.]+}|:[\w.]+/g, '[:{]([\\w]+)}?')
  const pathNameRegex = new RegExp(`^${pathExpressionPattern}$`)
  const pathNames = pathNameRegex.exec(pathExpression)
  return pathNames && pathNames.length > 0 ? pathNames.slice(1) : null
}

Move from dynamic imports to static imports?

Hi

Index.ts uses dynamic importing to dynamically load only the processors that are needed.

This causes problems with bundling, such as esbuild, because they are unable to bundle the required files.

Could this be changed to something like this:

import * as ProxyIntegration from "aws-lambda-router/lib/proxyIntegration";
import * as SnsIntegration from "aws-lambda-router/lib/sns";
import * as SqsIntegration from "aws-lambda-router/lib/sqs";
import * as S3Integration from "aws-lambda-router/lib/s3";

const processors = {
  proxyIntegration: ProxyIntegration,
  sns: SnsIntegration,
  sqs: SqsIntegration,
  s3: S3Integration,
};

const extractEventProcessorMapping = (routeConfig: RouteConfig) => {
  const processorMap = new Map<string, EventProcessor>();
  for (const key of Object.keys(routeConfig)) {
    if (key === "debug" || key === "onError") {
      continue;
    }
    try {
      const processor = processors[key];
      if (!processor) {
        throw new Error(`Could not find processor ${key}`);
      }
      processorMap.set(key, processor);
    } catch (error) {
      throw new Error(
        `The event processor '${key}', that is mentioned in the routerConfig, cannot be instantiated (${error.toString()})`
      );
    }
  }
  return processorMap;
};

This way imports are static, and esbuild could bundle the project.

error mapping is not working

I have added the below code
debug: true,
errorMapping: {
'NotFound': 404,
'ServerError': 500,
},

but it fails to map the error.

Question: When is the next planned release?

Hello,

A recent PR I created was merged and I was wondering what the timeline is for it to be released?
I'm also interested in helping to maintain this project.

Thanks,
Ethan

Question: When will a 6.3 release be pushed to NPM?

Hey all,

I'm really liking the simplicity of this package. I ran into a snag the other night because the latest published version of the package on NPM (0.6.2) didn't contain a change that is currently in the master branch. Specifically, the change to the proxy integrations's isLocalExecution implementation.
https://github.com/spring-media/aws-lambda-router/blame/master/lib/proxyIntegration.js#L203

I would like to avoid adding a specific commit to our package.json or forking the repo.

Thanks for the consideration!

Unexpected behaviour with API-Gateway Custom Domain Name Mapping

Hi,

Recently we found out that the first parameter from the event.path was ignored. After a quick inspectation of the lib/proxyIntegration.js file, I found an "ugly hack" which caused this behaviour, along with the following comment:

ugly hack: if host is from API-Gateway 'Custom Domain Name Mapping', then event.path has the value '/basepath/resource-path/';
if host is from amazonaws.com, then event.path is just '/resource-path':

My guess is this function is necessary for cases where users of aws-lambda-router use the API-gateway like this: http://customdomain.com/[stage]/[...parameters] (i.e. using the stage in between the custom domain and the parameters). However, in my case only the prod stage should be published on the custom domain. That's why I created a base path mapping in my Custom Domain options which maps the / path to mylambdafunctionname:prod (the prod stage of my API). This is why I don't have the stage name in my custom url and I think this is what causes the code to (in my case undesirable) remove the first parameter. But this is just my guess. I'm not entirely sure what is meant by basepath in the "ugly hack" comment, but regardless of whether it's because of me not having the stage in my custom url or it's because of something else, my point regarding unexpected first parameter removal still stands.

I'd be glad to hear if more details are needed to debug this case.

Router does not work in SAM local

The url normalization breaks the url when running the lambda in SAM local.
This happens because when using sam local the host is set to localhost.

I ve sent a pull request to address the issue.
More details in the pr #20

Thanks,
Nicola

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.