Middlewares documentation

Available middlewares

cache

Offers a simple but flexible caching layer that allows to cache the response associated to a given event and return it directly (without running the handler) if the same event is received again in a successive execution.

By default, the middleware stores the cache in memory, so the persistence is guaranteed only for a short amount of time (the duration of the container), but you can use the configuration layer to provide your own caching implementation.

Options

Sample usage

// assumes the event contains a unique event identifier
const calculateCacheId = event => Promise.resolve(event.id)
// use in-memory storage as example
const myStorage = {}
// simulates a delay in retrieving the value from the caching storage
const getValue = key =>
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(myStorage[key]), 100)
  })
// simulates a delay in writing the value in the caching storage
const setValue = (key, value) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      myStorage[key] = value
      return resolve()
    }, 100)
  })

const originalHandler = (event, context, cb) => {
  /* ... */
}

const handler = middy(originalHandler).use(
  cache({
    calculateCacheId,
    getValue,
    setValue
  })
)

cors

Sets CORS headers (Access-Control-Allow-Origin, Access-Control-Allow-Headers, Access-Control-Allow-Credentials), necessary for making cross-origin requests, to the response object.

Sets headers in after and onError phases.

Options

Sample usage

const middy = require('middy')
const { cors } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

handler.use(cors())

// when Lambda runs the handler...
handler({}, {}, (_, response) => {
  expect(response.headers['Access-Control-Allow-Origin']).toEqual('*')
})

doNotWaitForEmptyEventLoop

Sets context.callbackWaitsForEmptyEventLoop property to false. This will prevent Lambda from timing out because of open database connections, etc.

Options

By default the middleware sets the callbackWaitsForEmptyEventLoop property to false only in the before phase, meaning you can override it in handler to true if needed. You can set it in all steps with the options:

Sample Usage

const middy = require('middy')
const { doNotWaitForEmptyEventLoop } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

handler.use(doNotWaitForEmptyEventLoop({ runOnError: true }))

// When Lambda runs the handler it gets context with callbackWaitsForEmptyEventLoop property set to false

handler(event, context, (_, response) => {
  expect(context.callbackWaitsForEmptyEventLoop).toEqual(false)
})

functionShield

Hardens AWS Lambda execution environment:

More info:

Get a free token

Please visit: https://www.puresec.io/function-shield-token-form

Modes

Options

Sample Usage

'use strict';

const fs = require('fs');
const middy = require('middy');
const {ssm, functionShield} = require('middy/middlewares');

async function hello(event) {
  fs.openSync('/tmp/test', 'w');
}


const handler = middy(hello)
  .use(ssm({
    cache: true,
    setToContext: true,
    names: {
      FUNCTION_SHIELD_TOKEN: 'function_shield_token'
    }
  }))
  .use(functionShield(
    {
      policy: {
        outbound_connectivity: 'alert'
      }
    }
  ));

module.exports = {
  handler
};
START RequestId: f7b7305d-d785-11e8-baf1-9136b5c7aa75 Version: $LATEST
[TOKEN VERIFICATION] license is OK
{"function_shield":true,"policy":"read_write_tmp","details":{"path":"/tmp/test"},"mode":"block"}
2018-10-24 15:11:45.427 (+03:00)        f7b7305d-d785-11e8-baf1-9136b5c7aa75    {"errorMessage":"Unknown system error -999: Unknown system error -999, open '/tmp/test'","errorType":"Error","stackTrace":["Object.fs.openSync (fs.js:646:18)","Function.hello (/var/task/handler.js:8:6)","runMiddlewares (/var/task/node_modules/middy/src/middy.js:180:42)","runNext (/var/task/node_modules/middy/src/middy.js:85:14)","before (/var/task/node_modules/middy/src/middlewares/functionShield.js:20:5)","runNext (/var/task/node_modules/middy/src/middy.js:70:24)","<anonymous>","process._tickDomainCallback (internal/process/next_tick.js:228:7)"]}
END RequestId: f7b7305d-d785-11e8-baf1-9136b5c7aa75
REPORT RequestId: f7b7305d-d785-11e8-baf1-9136b5c7aa75  Duration: 458.65 ms     Billed Duration: 500 ms         Memory Size: 1024 MB    Max Memory Used: 38 MB  

httpContentNegotiation

Parses Accept-* headers and provides utilities for HTTP content negotiation (charset, encoding, language and media type).

By default the middleware parses charsets (Accept-Charset), languages (Accept-Language), encodings (Accept-Encoding) and media types (Accept) during the before phase and expands the event object by adding the following properties:

This middleware expects the headers in canonical format, so it should be attached after the httpHeaderNormalizer middleware. It also can throw an HTTP exception, so it can be convenient to use it in combination with the httpErrorHandler.

Options

Sample Usage

const middy = require('middy')
const {
  httpContentNegotiation,
  httpHeaderNormalizer,
  httpErrorHandler
} = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  let message, body

  switch (event.preferredLanguage) {
    case 'it-it':
      message = 'Ciao Mondo'
      break
    case 'fr-fr':
      message = 'Bonjour le monde'
      break
    default:
      message = 'Hello world'
  }

  switch (event.preferredMediaType) {
    case 'application/xml':
      body = `<message>${message}</message>`
      break
    case 'application/yaml':
      body = `---\nmessage: ${message}`
      break
    case 'application/json':
      body = JSON.stringify({ message })
      break
    default:
      body = message
  }

  return cb(null, {
    statusCode: 200,
    body
  })
})

handler
  .use(httpHeaderNormalizer())
  .use(
    httpContentNegotiation({
      parseCharsets: false,
      parseEncodings: false,
      availableLanguages: ['it-it', 'fr-fr', 'en'],
      availableMediaTypes: [
        'application/xml',
        'application/yaml',
        'application/json',
        'text/plain'
      ]
    })
  )
  .use(httpErrorHandler())

module.exports = { handler }

httpErrorHandler

Automatically handles uncaught errors that are created with http-errors and creates a proper HTTP response for them (using the message and the status code provided by the error object).

It should be set as the last error handler.

Options

Sample usage

const middy = require('middy')
const { httpErrorHandler } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  throw new createError.UnprocessableEntity()
})

handler.use(httpErrorHandler())

// when Lambda runs the handler...
handler({}, {}, (_, response) => {
  expect(response).toEqual({
    statusCode: 422,
    body: 'Unprocessable Entity'
  })
})

httpEventNormalizer

If you need to access the query string or path parameters in an API Gateway event you can do so by reading the attributes in event.queryStringParameters and event.pathParameters, for example: event.pathParameters.userId. Unfortunately if there are no parameters for these parameter holders, the relevant key queryStringParameters or pathParameters won’t be available in the object, causing an expression like event.pathParameters.userId to fail with the error: TypeError: Cannot read property 'userId' of undefined.

A simple solution would be to add an if statement to verify if the pathParameters (or queryStringParameters) exists before accessing one of its parameters, but this approach is very verbose and error prone.

This middleware normalizes the API Gateway event, making sure that an object for queryStringParameters and pathParameters is always available (resulting in empty objects when no parameter is available), this way you don’t have to worry about adding extra if statements before trying to read a property and calling event.pathParameters.userId will result in undefined when no path parameter is available, but not in an error.

Sample usage

const middy = require('middy')
const { httpEventNormalizer } = require('middy/httpEventNormalizer')

const handler = middy((event, context, cb) => {
  console.log('Hello user #{event.pathParameters.userId}') // might produce `Hello user #undefined`, but not an error
  cb(null, {})
})

handler.use(httpEventNormalizer())

httpHeaderNormalizer

Normalizes HTTP header names to their canonical format. Very useful if clients are not using the canonical names of header (e.g. content-type as opposed to Content-Type).

API Gateway does not perform any normalization, so the headers are propagated to Lambda exactly as they were sent by the client.

Other middlewares like jsonBodyParser or urlEncodeBodyParser will rely on headers to be in the canonical format, so if you want to support non-normalized headers in your app you have to use this middleware before those ones.

This middleware will copy the original headers in event.rawHeaders.

Options

Sample usage

const middy = require('middy')
const {
  httpHeaderNormalizer,
  jsonBodyParser,
  urlEncodeBodyParser
} = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

handler
  .use(httpHeaderNormalizer())
  .use(jsonBodyParser())
  .use(urlEncodeBodyParser())

httpPartialResponse

Filtering the data returned in an object or JSON stringified response has never been so easy. Add the httpPartialResponse middleware to your middleware chain, specify a custom filteringKeyName if you want to and that’s it. Any consumer of your API will be able to filter your JSON response by adding a querystring key with the fields to filter such as fields=firstname,lastname.

This middleware is based on the awesome json-mask package written by Yuriy Nemtsov

const middy = require('middy')
const { httpPartialResponse } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  const response = {
    statusCode: 200,
    body: {
      firstname: 'John',
      lastname: 'Doe',
      gender: 'male',
      age: 30,
      address: {
        street: 'Avenue des Champs-Élysées',
        city: 'Paris'
      }
    }
  }

  cb(null, response)
})

handler.use(httpPartialResponse())

const event = {
  queryStringParameters: {
    fields: 'firstname,lastname'
  }
}

handler(event, {}, (_, response) => {
  expect(response.body).toEqual({
    firstname: 'John',
    lastname: 'Doe'
  })
})

httpSecurityHeaders

Applies best practice security headers to responses. It’s a simplified port of HelmetJS. See HelmetJS documentation of option details.

Options

Sample usage

const middy = require('middy')
const { httpSecurityHeaders } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

handler.use(httpSecurityHeaders())

jsonBodyParser

Automatically parses HTTP requests with a JSON body and converts the body into an object. Also handles gracefully broken JSON as UnprocessableEntity (422 errors) if used in combination with httpErrorHandler.

It can also be used in combination with validator as a prior step to normalize the event body input as an object so that the content can be validated.

Note: by default this is going to parse only events that contain the header Content-Type (or content-type) set to application/json. If you want to support different casing for the header name (e.g. Content-type) then you should use the httpHeaderNormalizer middleware before this middleware.

const middy = require('middy')
const { jsonBodyParser } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

handler.use(jsonBodyParser())

// invokes the handler
const event = {
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ foo: 'bar' })
}
handler(event, {}, (_, body) => {
  expect(body).toEqual({ foo: 'bar' })
})

s3KeyNormalizer

Normalizes key names in s3 events.

S3 events like S3 PUT and S3 DELETE will contain in the event a list of the files that were affected by the change.

In this list the file keys are encoded in a very peculiar way (urlencoded and space characters replaced by a +). Very often you will use the key directly to perform operations on the file using the AWS S3 SDK, in which case it’s very easy to forget to decode the key correctly.

This middleware, once attached, makes sure that every S3 event has the file keys properly normalized.

Sample usage

const middy = require('middy')
const { s3KeyNormalizer } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  // use the event key directly without decoding it
  console.log(event.Records[0].s3.object.key)

  // return all the keys
  callback(null, event.Records.map(record => record.s3.object.key))
})

handler.use(s3KeyNormalizer())

secretsManager

Fetches parameters from AWS Secrets Manager.

Secrets to fetch can be defined by by name. See AWS docs here.

Secrets are assigned to the function handler’s context object.

The Middleware makes a single API request for each secret as Secrets Manager does not support batch get.

For each secret, you also provide the name under which its value should be added to context.

Options

NOTES:

Sample Usage

Simplest usage, exports parameters as environment variables.

const middy = require('middy')
const { secretsManager } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

handler.use(
  secretsManager({
    cache: true,
    secrets: {
      RDS_LOGIN: 'dev/rds_login'
    }
  })
)

// Before running the function handler, the middleware will fetch from Secrets Manager
handler(event, context, (_, response) => {
  // assuming the dev/rds_login has two keys, 'Username' and 'Password'
  expect(context.RDS_LOGIN.Username).toEqual('username')
  expect(context.RDS_LOGIN.Password).toEqual('password')
})

ssm

Fetches parameters from AWS Systems Manager Parameter Store.

Parameters to fetch can be defined by path and by name (not mutually exclusive). See AWS docs here.

By default parameters are assigned to the Node.js process.env object. They can instead be assigned to the function handler’s context object by setting the setToContext flag to true. By default all parameters are added with uppercase names.

The Middleware makes a single API request to fetch all the parameters defined by name, but must make an additional request per specified path. This is because the AWS SDK currently doesn’t expose a method to retrieve parameters from multiple paths.

For each parameter defined by name, you also provide the name under which its value should be added to process.env or context. For each path, you instead provide a prefix, and by default the value from each parameter returned from that path will be added to process.env or context with a name equal to what’s left of the parameter’s full name after the defined path, with the prefix prepended. If the prefix is an empty string, nothing is prepended. You can override this behaviour by providing your own mapping function with the getParamNameFromPath config option.

Options

NOTES:

Sample Usage

Simplest usage, exports parameters as environment variables.

const middy = require('middy')
const { ssm } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

handler.use(
  ssm({
    cache: true,
    paths: {
      SOME_PREFIX_: '/dev/db'
    },
    names: {
      SOME_ACCESS_TOKEN: '/dev/service_name/access_token'
    }
  })
)

// Before running the function handler, the middleware will fetch SSM params
handler(event, context, (_, response) => {
  expect(process.env.SOME_PREFIX_CONNECTION_STRING).toEqual(
    'some-connection-string'
  ) // The '/dev/db' path contains the CONNECTION_STRING parameter
  expect(process.env.SOME_ACCESS_TOKEN).toEqual('some-access-token')
})

Export parameters to context object, override AWS region.

const middy = require('middy')
const { ssm } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

handler.use(
  ssm({
    cache: true,
    names: {
      SOME_ACCESS_TOKEN: '/dev/service_name/access_token'
    },
    awsSdkOptions: { region: 'us-west-1' },
    setToContext: true
  })
)

handler(event, context, (_, response) => {
  expect(context.SOME_ACCESS_TOKEN).toEqual('some-access-token')
})

validator

Automatically validates incoming events and outgoing responses against custom schemas defined with the JSON schema syntax.

If an incoming event fails validation a BadRequest error is raised. If an outgoing response fails validation a InternalServerError error is raised.

This middleware can be used in combination with httpErrorHandler to automatically return the right response to the user.

It can also be used in combination with httpcontentnegotiation to load localised translations for the error messages (based on the currently requested language). This feature uses internally ajv-i18n module, so reference to this module for options and more advanced use cases. By default the language used will be English (en), but you can redefine the default language by passing it in the ajvOptions options with the key defaultLanguage and specifying as value one of the supported locales.

Options

Sample Usage

Example for input validation:

const middy = require('middy')
const { validator } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

const schema = {
  required: ['body', 'foo'],
  properties: {
    // this will pass validation
    body: {
      type: 'string'
    },
    // this won't as it won't be in the event
    foo: {
      type: 'string'
    }
  }
}

handler.use(
  validator({
    inputSchema: schema
  })
)

// invokes the handler, note that property foo is missing
const event = {
  body: JSON.stringify({ something: 'somethingelse' })
}
handler(event, {}, (err, res) => {
  expect(err.message).toEqual('Event object failed validation')
})

Example for output validation:

const middy = require('middy')
const { validator } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, {})
})

const schema = {
  required: ['body', 'statusCode'],
  properties: {
    body: {
      type: 'object'
    },
    statusCode: {
      type: 'number'
    }
  }
}

handler.use(validator({ outputSchema: schema }))

handler({}, {}, (err, response) => {
  expect(err).not.toBe(null)
  expect(err.message).toEqual('Response object failed validation')
  expect(response).not.toBe(null) // it doesn't destroy the response so it can be used by other middlewares
})

urlEncodeBodyParser

Automatically parses HTTP requests with URL-encoded body (typically the result of a form submit).

Options

Sample Usage

const middy = require('middy')
const { urlEncodeBodyParser } = require('middy/middlewares')

const handler = middy((event, context, cb) => {
  cb(null, event.body) // propagates the body as response
})

handler.use(urlEncodeBodyParser({ extended: false }))

// When Lambda runs the handler with a sample event...
const event = {
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: 'frappucino=muffin&goat%5B%5D=scone&pond=moose'
}

handler(event, {}, (_, body) => {
  expect(body).toEqual({
    frappucino: 'muffin',
    'goat[]': 'scone',
    pond: 'moose'
  })
})

warmup

Warmup middleware that helps to reduce the cold-start issue. Compatible by default with serverless-plugin-warmup, but it can be configured to suit your implementation.

This middleware allows you to specify a schedule to keep Lambdas that always need to be very responsive warmed-up. It does this by regularly invoking the Lambda, but will terminate early to avoid the actual handler logic from being run.

If you use serverless-plugin-warmup the scheduling part is done by the plugin and you just have to attach the middleware to your “middyfied” handler. If you don’t want to use the plugin you have to create the schedule yourself and define the isWarmingUp function to define wether the current event is a warmup event or an actual business logic execution.

Options

Sample usage

const isWarmingUp = event => event.isWarmingUp === true
const onWarmup = event => console.log('I am just warming up', event)

const originalHandler = (event, context, cb) => {
  /* ... */
}

const handler = middy(originalHandler).use(
  warmup({
    isWarmingUp,
    onWarmup
  })
)