/lambda-middleware

A collection of middleware for AWS lambda functions.

Primary LanguageTypeScriptMIT LicenseMIT

@lambda-middleware

open issues debug build status codecov CLA assistant

This monorepo is a collection of middleware for AWS lambda functions.

Middlewares

Other packages

Furthermore there is utility collection available at @lambda-middleware/utils.downloads

Usage

Each middleware is a higher-order function that can be wrapped around the handler function.

export const handler = someMiddleware()(() => {
  return {
    body: '',
    statusCode: 200
  }
})

Each middleware is build as

(options) => (handler) => (event, context) => Response

This means that middleware can be composed and piped like any other function with only one parameter (the handler). This library contains a helper for composing, but any other implementation should work as well.

export const handler = compose(
  someMiddleware(),
  someOtherMiddleware(),
  aThirdMiddleware()
)(() => {
  return {
    body: '',
    statusCode: 200
  }
})

There's a known issue with TypeScript that pipe and compose functions cannot infer types correctly if the innermost function is generic (in this case the last argument to compose). If you use TypeScript in strict mode, you can instead use the composeHandler function exported from @lambda-middleware/compose:

export const handler = composeHandler(
  someMiddleware(),
  someOtherMiddleware(),
  aThirdMiddleware(),
  () => {
    return {
      body: '',
      statusCode: 200
    }
  }
)

Composing middleware is equivalent to calling it nested:

export const handler =
  someMiddleware()(
    someOtherMiddleware()(
      aThirdMiddleware()(() => {
        return {
          body: '',
          statusCode: 200
        }
      })
    )
  )

The order of composition can be relevant. When using a helper to do the composition, check, in which order the functions are applied. Most of the time TypeScript should be able to warn you, if the order is wrong.

Imagine middleware as an onion around your function: The outermost middleware will get called first before the handler starts, and last after the handler finishes or throws an error. In our example above the order in which middleware gets executed therefore would be:

someMiddleware
  someOtherMiddleware
    aThirdMiddleware
      the handler
    aThirdMiddleware
  someOtherMiddleware
someMiddleware

This means that middleware which transforms the input for the handler will be executed top to bottom, while middleware that transforms the response will be called bottom to top.

Writing your own middleware

If you want to write your own middleware, check the existing examples and feel free to borrow some of the tests for inspiration. The general idea for a middleware is the following:

const myMiddleware = (optionsForMyMiddleware) => (handler) => async (event, context) => {
  try {
    const modifiedEvent = doSomethingBeforeCallingTheHandler(event)
    const response = await handler(modifiedEvent, context)
    const modifiedResponse = doSomethingAfterCallingTheHandler(response)
    return modifiedResponse
  } catch (error) {
    const modifiedError = doSomethingInCaseOfAnError(error)
    throw modifiedError
  }
}

Usually the same middleware should not need to do something before the handler, after the handler and on error. Creating separated middlewares for these cases keeps them more versatile. But cases that require multiple steps are supported as well.

Since the middlewares only uses function composition, TypeScript can offer extensive typing support to let you know how the middleware changed. When adding your own middleware it is recommended to use generics to avoid losing type information.

Instead of

const bodyParser = () =>
  (handler: PromiseHandler<Omit<APIGatewayProxyEvent, body> & { body: object}, APIGatewayProxyResult>): PromiseHandler<APIGatewayProxyEvent, APIGatewayProxyResult> =>
  async (event: E, context: Context) => {
  return handler({ ...event, body: JSON.parse(event.body) }, context)
}

use

const bodyParser = () =>
  <E extends APIGatewayProxyEvent>(handler: PromiseHandler<Omit<E, body> & { body: object}, APIGatewayProxyResult>): PromiseHandler<E, APIGatewayProxyResult> =>
  async (event: E, context: Context) => {
  return handler({ ...event, body: JSON.parse(event.body) }, context)
}

so that if multiple middlewares change the event, the resulting type will have all changes and not just the latest.

Contributing

If you want to contribute to the project, please read our contributing guidelines first.