Skip to content

Instantly share code, notes, and snippets.

@timoschinkel
Last active March 17, 2023 12:22
Show Gist options
  • Save timoschinkel/23fa06d504bce4a0acd28c4fe593c78c to your computer and use it in GitHub Desktop.
Save timoschinkel/23fa06d504bce4a0acd28c4fe593c78c to your computer and use it in GitHub Desktop.
AWS lambda generic middleware enabled handlers
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
export class ApiGatewayCorrelationContextMiddleware implements IMiddleware<APIGatewayProxyEvent, APIGatewayProxyResult> {
public async process(event: APIGatewayProxyEvent, next: Handler<APIGatewayProxyEvent, APIGatewayProxyResult>): Promise<APIGatewayProxyResult> {
return next(event);
}
}
class SomeMiddleware implements IMiddleware<APIGatewayProxyEvent, APIGatewayProxyResult> {
public async process(event: APIGatewayProxyEvent, next: EventHandler<APIGatewayProxyEvent, APIGatewayProxyResult>): Promise<APIGatewayProxyResult> {
return next(event);
}
}
const apiGatewayHandler = new MiddlewareHandler<APIGatewayProxyEvent, APIGatewayProxyResult>(
new SomeMiddleware()
);
/* Usage: */
export const MyApiGatewayTriggeredHandler = apiGatewayHandler.handle(async () => {
console.log('We are here!');
return {
statusCode: 200,
body: 'OK',
};
});
describe('EventHandler', () => {
describe('MiddlewareHandler', () => {
type Request = { middlewares: string[] };
type Response = { middlewares: string[] };
class SimpleMiddleware implements IMiddleware<Request, Response> {
private readonly identifier: string;
constructor (identifier: string) {
this.identifier = identifier;
}
public async process(event, next): Promise<Response> {
event.middlewares.push(this.identifier);
return next(event)
.then(response => {
response.middlewares.push(this.identifier);
return response;
});
}
}
describe('handle()', () => {
it('should pass through all middleware defined in constructor', async () => {
const handler = new MiddlewareHandler<Request, Response>(
new SimpleMiddleware('one'),
new SimpleMiddleware('two')
);
// Call the handler
const response = await handler.handle(async (request) => request)({ middlewares: [] });
expect(response.middlewares).toEqual(['one', 'two', 'two', 'one']);
});
it('should pass through all middleware defined in constructor and middleware defined with `use`', async () => {
const handler = new MiddlewareHandler<Request, Response>(
new SimpleMiddleware('one')
).use(new SimpleMiddleware('two'));
// Call the handler
const response = await handler.handle(async (request) => request)({ middlewares: [] });
expect(response.middlewares).toEqual(['one', 'two', 'two', 'one']);
});
});
describe('use()', () => {
it('should create a new instance of handler', async () => {
const handler = new MiddlewareHandler<Request, Response>(
new SimpleMiddleware('one')
);
const withUse = handler.use(new SimpleMiddleware('two'));
expect(handler).not.toEqual(withUse);
});
});
});
});
export type EventHandler<I, O> = (event: I) => Promise<O>;
export interface IMiddleware<I, O> {
process(event: I, next: Handler<I, O>): Promise<O>;
}
export class MiddlewareHandler<I, O> {
private readonly middleware: IMiddleware<I, O>[];
constructor(...middleware: IMiddleware<I, O>[]) {
this.middleware = middleware;
}
public use(middleware: IMiddleware<I, O>): MiddlewareHandler<I, O> {
return new MiddlewareHandler<I, O>(...this.middleware, middleware);
}
public handle(implementation: EventHandler<I, O>): EventHandler<I, O> {
return async (event) => {
if (this.middleware.length > 0) {
// pass through middleware
const current = this.middleware[0];
return current.process(
event,
new MiddlewareHandler<I, O>(...this.middleware.slice(1)).handle(implementation)
);
}
return implementation(event);
};
}
}
import { SQSEvent } from 'aws-lambda';
const sqsHandler = new MiddlewareHandler<SQSEvent, void>(
new SqsCorrelationContextMiddleware()
);
class SomeMiddleware implements IMiddleware<SQSEvent, void> {
public async process(event: SQSEvent, next: EventHandler<SQSEvent, void>): Promise<void> {
return next(event);
}
}
/* Usage: */
export const MySqsTriggeredHandler = sqsHandler.handle(async () => {
console.log('We are here!');
// resolve, in case of an error, we can throw anything and AWS will requeue
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment