Skip to content

Feature request: Implement middleware composition in event handlerย #4427

@svozza

Description

@svozza

Use case

Customers should be able to register multiple middlewares and have them applied in the order they were added. As all middleware share the same type signature this is ultimately just function composition. Using the familiar pattern of using a callback, typically named next to proceeed to the next middleware, users should also have the option to shortcircuit the composition and return early from the middleware chain.

Solution/User Experience

Register middleware

Users can register middleware with the use method:

app.use(async (params, options, next) => {
    console.log('middleware1-start');
    await next();
    console.log('middleware1-end');
});

app.use(async (params, options, next) => {
    console.log('middleware2-start');
    await next();
    console.log('middleware2-end');
});

app.get('/test', async () => {
    console.log('handler');
    return { success: true };
});

const res = await app.resolve(createTestEvent('/test', 'GET'), context);

/** prints
 'middleware1-start',
 'middleware2-start',
 'handler',
 'middleware2-end',
 'middleware1-end',
 **/   

Compose middleware

We expose a function called composeMiddleware, which allows users to create re-usuable middleware stacks and we use this function in BaseRouter itself to chain the middleware.

import {composeMiddleware} from 'from '@aws-lambda-powertools/event-handler/rest/utils.jsl';'

const addCustomHeaders: Middleware = async (params, { request, event }, next) => {
  request.headers.set('x-request-id', event.requestContext.requestId);
  request.headers.set('x-source-ip', event.requestContext.identity.sourceIp);
  
  return next();
};

const logRequest: Middleware = async (params, { request, event }, next) => {
  console.log(`Processing ${event.httpMethod} ${event.path} from ${event.requestContext.identity.sourceIp}`);
  
  return next();
};

const customMiddleware = composeMiddleware([addCustomHeaders, logRequest]);

app.use(customMiddleware)

Shortcircuit

app.use(async (params, options, next) => {
    await next();
});

app.use(async (params, options, next) => {
    return new Response('Short-circuited', { status: 401 });
});

app.get('/test', async () => {
    return { success: true };
});

// Act
const result = await app.resolve(
    createTestEvent('/test', 'GET'),
    context
);

// result is 401 response not 200 with { success: true } body

Alternative solutions

Acknowledgment

Future readers

Please react with ๐Ÿ‘ and your use case to help us understand customer demand.

Sub-issues

Metadata

Metadata

Assignees

Labels

completedThis item is complete and has been merged/shippedevent-handlerThis item relates to the Event Handler Utilityfeature-requestThis item refers to a feature request for an existing or new utility

Type

No type

Projects

Status

Shipped

Relationships

None yet

Development

No branches or pull requests

Issue actions