middyjs / middy

🛵 The stylish Node.js middleware engine for AWS Lambda 🛵
https://middy.js.org
MIT License
3.7k stars 374 forks source link

Conditional middleware #1207

Closed Dobby89 closed 4 months ago

Dobby89 commented 4 months ago

Is your feature request related to a problem? Please describe.

I need a way to conditionally apply middleware to a lambda.

In my case, the lambda can be triggered by both APIGatewayProxyEvent or SQSEvent so I only need to apply CORS middleware (@middy/http-cors) to the lambda if the event is APIGatewayProxyEvent. Applying CORS to the lambda when it's triggered by SQSEvent breaks it because the SQSEvent is incompatible with the CORS middleware.

I've done quite a bit of searching on the official docs and elsewhere but can't find any examples or built-in mechanisms to conditionally apply middleware based on the runtime value of the lambda request.

Describe the solution you'd like

I'm guessing a middleware wrapper function could be written to conditionally apply the middleware, but I don't know enough about middy and its types to create the desired conditionalMiddleware function.

import middy from '@middy/core';
import middyCors from '@middy/http-cors';
import middyJsonBodyParser from '@middy/http-json-body-parser';
import { APIGatewayProxyEvent, SQSEvent } from 'aws-lambda';

const handler = async (event: APIGatewayProxyEvent | SQSEvent) => {
    //...
};

export const middyHandler = middy(handler)
    .use(middyJsonBodyParser())
    .use(
        conditionalMiddleware(
            (request) => 'httpMethod' in request.event, // Only apply middyCors when lambda is triggered by a APIGatewayProxyEvent
            middyCors()
        )
    );

Describe alternatives you've considered

Possibly registering two lambdas, which call a common processing function

import middy from '@middy/core';
import middyCors from '@middy/http-cors';
import middyJsonBodyParser from '@middy/http-json-body-parser';
import { APIGatewayProxyEvent, SQSEvent } from 'aws-lambda';
import middySqsJsonBodyParser from 'sqs-json-body-parser'

const process = async (event: APIGatewayProxyEvent | SQSEvent) => {
    if ('httpMethod' in event) {
        // Handle HTTP request
    }
    if ('Records' in event) {
        // Handle SQS message
    }
}

const apiGatewayHandler = async (event: APIGatewayProxyEvent) => {
    return await process(event);
};

const sqsHandler = async (event: SQSEvent) => {
    return await process(event);
};

export const middyApiGatewayHandler = middy(apiGatewayHandler)
    .use(middyJsonBodyParser())
    .use(middyCors()));

export const middySqsHandler = middy(sqsHandler)
    .use(middySqsJsonBodyParser())

Additional context

My lambda is triggered by APIGatewayProxyEvent and SQSEvent for resilience. If/when the API gateway request fails, the failure state is added onto an SQS queue and gets retried.

willfarrell commented 4 months ago

Middy doesn't support this, and has no plans to. We recommend only one event type per lambda. This also ensures your IAM is as granular as possible. You can have the business logic shared between two lambdas, but deployed separately. Alternatively if it really needs to be in the same lambda you could use different handler identifiers, the default in our docs is handler.

Dobby89 commented 4 months ago

Thanks for the guidance.

I opted for two separate lambdas in the end.

different handler identifiers

^^ Please could you link to the relevant section of the docs? I'm curious.

willfarrell commented 4 months ago

https://middy.js.org/docs/intro/getting-started#example

All examples use export const handler, but the lambda code can have multiple by changing handler to something else and then configuring to use that instead. It allows you to ship multiple handlers in a single bundle, but this approach will have slower cold starts.