Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docs/utils/MIDDYFY.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,3 +269,78 @@ The handler is wrapped with two middlewares.

1. The `event-normalizer` middleware performs a JSON parse on the `body`.
2. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.

## Creating a Lambda event handler

Use a Lambda event handler to process events from direct invocations from another Lambda function.

> **NOTE:** This is generally considered to be an anti-pattern in serverless application design. See [Lambda functions calling Lambda functions](https://docs.aws.amazon.com/lambda/latest/operatorguide/functions-calling-functions.html) in the AWS Lambda guide. Consider SQS queues or Step Functions as an alternative.

To create a Lambda event handler, you will need to create two modules: the handler function and the middyfy wrapper. You may optionally create a `schema.ts` module which provides a Joi validation schema for the `LambdaEvent`.

Create the AWS Lambda Handler function with the usual signature...

_/handlers/task-lambda/handler.ts_

```ts
import { Context } from 'aws-lambda';
import { LambdaEvent, LambdaResult, middyfyLambda } from '@leanstacks/serverless-common';
...

type FindTaskEvent = {
taskId: string;
};

type FindTaskResult = {
task?: Task;
};

export const handler = async (
event: LambdaEvent<FindTaskEvent>,
context: Context,
): Promise<LambdaResult<FindTaskResult>> => {
try {
// handle request
const { taskId } = event;
const task = await TaskService.findById(taskId);

// format and return response
if (task) {
return {
status: 200,
statusText: 'OK',
data: { task },
};
} else {
return {
status: 404,
statusText: 'Not Found',
data: {},
};
}
} catch (error) {
console.warn(`Failed to find Task. Detail: ${error}`);
return {
status: 500,
statusText: `${error}`,
data: {},
};
}
};
```

Next, we need to _"middyfy"_ the handler function. This is just a fancy way of saying we are wrapping the handler function with middleware.

_/handlers/task-lambda/index.ts_

```ts
import { middyfyLambda } from '@leanstacks/serverless-common';

import { handler } from './handler';

export const handle = middyfyLambda({ handler, logger: console.log });
```

The handler is wrapped with one middleware.

1. The `validator` middleware will validate the event when an `eventSchema` is provided in the options.
26 changes: 25 additions & 1 deletion src/utils/__tests__/middyfy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ import {
ScheduledEvent,
} from 'aws-lambda';

import { middyfyAPIGateway, middyfySNS, middyfySQS, middyfyScheduled } from '../middyfy';
import {
LambdaEvent,
LambdaResult,
middyfyAPIGateway,
middyfyLambda,
middyfySNS,
middyfySQS,
middyfyScheduled,
} from '../middyfy';

describe('middyfyAPIGateway', () => {
const handlerFn = async (
Expand Down Expand Up @@ -58,3 +66,19 @@ describe('middyfySQS', () => {
expect(handler).toBeDefined();
});
});

describe('middyfyLambda', () => {
const handlerFn = async (event: LambdaEvent, context: Context): Promise<LambdaResult> => {
return {
status: 200,
statusText: 'OK',
data: 'test',
};
};

it('should middyfyLambda', () => {
const handler = middyfyLambda({ handler: handlerFn });

expect(handler).toBeDefined();
});
});
5 changes: 5 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
export {
LambdaEvent,
LambdaResult,
LambdaHandler,
APIGatewayHandlerFn,
APIGatewayMiddyfyOptions,
middyfyAPIGateway,
middyfyLambda,
middyfyScheduled,
middyfySNS,
middyfySQS,
MiddyfyOptions,
LambdaMiddyfyOptions,
ScheduledHandlerFn,
ScheduledMiddyfyOptions,
SNSMiddyfyOptions,
Expand Down
49 changes: 49 additions & 0 deletions src/utils/middyfy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,31 @@ export type APIGatewayHandlerFn = (
*/
export type ScheduledHandlerFn = (event: ScheduledEvent, context: Context) => Promise<void>;

/**
* A Lambda function for invocation by another Lambda function.
*/
export type LambdaEvent<TEvent = unknown> = TEvent;

/**
* A Lambda function result for invocation by another Lambda function.
*/
export type LambdaResult<TResult = unknown> = {
status: number;
statusText: string;
data: TResult;
};

/**
* The AWS Lambda handler function signature for Lambda events, i.e. Lambda to
* Lambda function invocations.
* Note: This is generally considered an anti-pattern. Search for another
* design pattern before utilizing Lambda-to-Lambda.
*/
export type LambdaHandler<TEvent = unknown, TResult = unknown> = (
event: LambdaEvent<TEvent>,
context: Context,
) => Promise<LambdaResult<TResult>>;

/**
* Base options for `middyfy` functions.
*/
Expand Down Expand Up @@ -67,6 +92,15 @@ export type SQSMiddyfyOptions = MiddyfyOptions<SQSHandler> & {
eventSchema?: ObjectSchema;
};

/**
* Options for middyfied Lambda event handler functions.
*/
export type LambdaMiddyfyOptions<TEvent = unknown, TResult = unknown> = MiddyfyOptions<
LambdaHandler<TEvent, TResult>
> & {
eventSchema?: ObjectSchema;
};

/**
* Wraps an AWS Gateway proxy event handler function in middleware, returning the
* AWS Lambda handler function.
Expand Down Expand Up @@ -122,3 +156,18 @@ export const middyfySQS = (options: SQSMiddyfyOptions) => {
.use(eventNormalizer())
.use(validator({ eventSchema: options.eventSchema, logger: options.logger }));
};

/**
* Wraps a Lambda event handler function in middleware, returning the AWS
* Lambda handler function.
* Note: Lambda-to-Lambda invocations are considered an anti-pattern. Search
* for an alternative approach.
* @param options - The `LambdaMiddyfyOptions` object.
* @returns A middyfied handler function.
* @see {@link https://docs.aws.amazon.com/lambda/latest/operatorguide/functions-calling-functions.html}
*/
export const middyfyLambda = <TEvent, TResult>(options: LambdaMiddyfyOptions<TEvent, TResult>) => {
return middy(options.handler).use(
validator({ eventSchema: options.eventSchema, logger: options.logger }),
);
};