- cache
- cors
- doNotWaitForEmptyEventLoop
- httpContentNegotiation
- httpErrorHandler
- httpEventNormalizer
- httpHeaderNormalizer
- httpPartialResponse
- httpSecurityHeaders
- jsonBodyParser
- s3KeyNormalizer
- ssm
- validator
- urlEncodeBodyParser
- warmup
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.
calculateCacheId
(function) (optional): a function that accepts theevent
object as a parameter and returns a promise that resolves to a string which is the cache id for the give request. By default the cache id is calculated asmd5(JSON.stringify(event))
.getValue
(function) (optional): a function that defines how to retrieve the value associated to a given cache id from the cache storage. It acceptskey
(a string) and returns a promise that resolves to the cached response (if any) or toundefined
(if the given key does not exists in the cache)setValue
(function) (optional): a function that defines how to set a value in the cache. It accepts akey
(string) and avalue
(response object). It must return a promise that resolves when the value has been stored.
// 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
}))
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.
origin
(string) (optional): origin to put in the header (default: "*
").origins
(array) (optional): An array of allowed origins. The incoming origin is matched against the list and is returned if present.headers
(string) (optional): value to put in Access-Control-Allow-Headers (default:null
)credentials
(bool) (optional): if true, sets theAccess-Control-Allow-Origin
as request headerOrigin
, if present (defaultfalse
)
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('*')
})
Sets context.callbackWaitsForEmptyEventLoop
property to false
.
This will prevent Lambda from timing out because of open database connections, etc.
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:
runOnBefore
(defaults totrue
) - sets property before running your handlerrunOnAfter
(defaults tofalse
)runOnError
(defaults tofalse
)
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)
})
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:
preferredCharsets
(array
) - The list of charsets that can be safely used by the app (as the result of the negotiation)preferredCharset
(string
) - The preferred charset (as the result of the negotiation)preferredEncodings
(array
) - The list of encodings that can be safely used by the app (as the result of the negotiation)preferredEncoding
(string
) - The preferred encoding (as the result of the negotiation)preferredLanguages
(array
) - The list of languages that can be safely used by the app (as the result of the negotiation)preferredLanguage
(string
) - The preferred language (as the result of the negotiation)preferredMediaTypes
(array
) - The list of media types that can be safely used by the app (as the result of the negotiation)preferredMediaType
(string
) - The preferred media types (as the result of the negotiation)
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
.
parseCharsets
(defaults totrue
) - Allows enabling/disabling the charsets parsingavailableCharsets
(defaults toundefined
) - Allows defining the list of charsets supported by the Lambda functionparseEncodings
(defaults totrue
) - Allows enabling/disabling the encodings parsingavailableEncodings
(defaults toundefined
) - Allows defining the list of encodings supported by the Lambda functionparseLanguages
(defaults totrue
) - Allows enabling/disabling the languages parsingavailableLanguages
(defaults toundefined
) - Allows defining the list of languages supported by the Lambda functionparseMediaTypes
(defaults totrue
) - Allows enabling/disabling the media types parsingavailableMediaTypes
(defaults toundefined
) - Allows defining the list of media types supported by the Lambda functionfailOnMismatch
(defaults totrue
) - If set to true it will throw an HTTPNotAcceptable
(406) exception when the negotiation fails for one of the headers (e.g. none of the languages requested are supported by the app)
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 }
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.
logger
(defaults toconsole.error
) - a logging function that is invoked with the current error as an argument. You can passfalse
if you don't want the logging to happen.
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'
})
})
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.
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())
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
.
normalizeHeaderKey
(function) (optional): a function that accepts an header name as a parameter and returns its canonical representation.
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())
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'
})
})
Applies best practice security headers to responses. It's a simplified port of HelmetJS. See HelmetJS documentation of option details.
dnsPrefetchControl
controls browser DNS prefetchingexpectCt
for handling Certificate Transparency [Future Feature]frameguard
to prevent clickjackinghidePoweredBy
to remove the Server/X-Powered-By headerhsts
for HTTP Strict Transport SecurityieNoOpen
sets X-Download-Options for IE8+noSniff
to keep clients from sniffing the MIME typereferrerPolicy
to hide the Referer headerxssFilter
adds some small XSS protections
const middy = require('middy')
const { httpSecurityHeaders } = require('middy/middlewares')
const handler = middy((event, context, cb) => {
cb(null, {})
})
handler
.use(httpSecurityHeaders())
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.
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'})
})
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.
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())
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.
cache
(boolean) (optional): Defaults tofalse
. Set it totrue
to skip further calls to AWS SSMcacheExpiryInMillis
(int) (optional): Defaults toundefined
. Use this option to invalidate cached parameter values from SSMpaths
(object) (optional*): Map of SSM paths to fetch parameters from, where the key is the prefix for the destination name, and value is the SSM path. Example:{paths: {DB_: '/dev/service/db'}}
names
(object) (optional*): Map of parameters to fetch from SSM, where the key is the destination, and value is param name in SSM. Example:{names: {DB_URL: '/dev/service/db_url'}}
awsSdkOptions
(object) (optional): Options to pass to AWS.SSM class constructor. Defaults to{ maxRetries: 6, retryDelayOptions: {base: 200} }
setToContext
(boolean) (optional): This will assign parameters to thecontext
object of the function handler rather than toprocess.env
. Defaults tofalse
NOTES:
- While you don't need both
paths
andnames
, you do need at least one of them! - Lambda is required to have IAM permissions for
ssm:GetParameters*
actions aws-sdk
version of2.176.0
or greater is required. If your project doesn't currently useaws-sdk
, you may need to install it as adevDependency
in order to run tests
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')
})
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.
inputSchema
(object) (optional): The JSON schema object that will be used to validate the input (handler.event
) of the Lambda handler.outputSchema
(object) (optional): The JSON schema object that will be used to validate the output (handler.response
) of the Lambda handler.ajvOptions
(object) (optional): Options to pass to ajv class constructor. Defaults are{v5: true, coerceTypes: 'array', $data: true, allErrors: true, useDefaults: true, defaultLanguage: 'en'}
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
})
Automatically parses HTTP requests with URL-encoded body (typically the result of a form submit).
extended
(boolean) (optional): iftrue
will useqs
to parse the body of the request. By default is set tofalse
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 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.
isWarmingUp
: a function that accepts theevent
object as a parameter and returnstrue
if the current event is a warmup event andfalse
if it's a regular execution. The default function will check if theevent
object has asource
property set toserverless-plugin-warmup
.onWarmup
: a function that gets executed before the handler exits in case of warmup. By default the function just prints:Exiting early via warmup Middleware
.
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
}))