Skip to main content

EventBridge (CloudWatch Events)

The adapter to handle requests from AWS EventBridge.

info

The option of responseWithErrors is ignored by this adapter and we always call resolver.fail with the error.

Typescript

To correctly type your body when receiving the AWS EventBridge request, you must install aws-lambda:

npm i --save-dev @types/aws-lambda

So when getting the body you should use this type:

eventbridge.controller.ts
import type { EventBridgeEvent } from 'aws-lambda';

If you want to integrate with Scheduled Expression, you can use this type:

eventbridge.controller.ts
import type { ScheduledEvent } from 'aws-lambda';

About the adapter

In AWS EventBridge, you don't have requests, you just receive the info from Cloudwatch Events within event property of the handler.

So, in order to handle this adapter, by default we create a POST request to /eventbridge with the body being the event property as JSON.

rds-eventbridge-event-example.json
{
"version": "0",
"id": "fe8d3c65-xmpl-c5c3-2c87-81584709a377",
"detail-type": "RDS DB Instance Event",
"source": "aws.rds",
"account": "123456789012",
"time": "2020-04-28T07:20:20Z",
"region": "us-east-2",
"resources": [
"arn:aws:rds:us-east-2:123456789012:db:rdz6xmpliljlb1"
],
"detail": {
"EventCategories": [
"backup"
],
"SourceType": "DB_INSTANCE",
"SourceArn": "arn:aws:rds:us-east-2:123456789012:db:rdz6xmpliljlb1",
"Date": "2020-04-28T07:20:20.112Z",
"Message": "Finished DB Instance backup",
"SourceIdentifier": "rdz6xmpliljlb1"
}
}

Normally, your framework will parse this JSON and return the parsed values as javascript objects.

Schedule Expression

With Schedule Expression, you have the following JSON when the event is triggered:

scheduled-eventbridge-event-example.json
{
"version": "0",
"account": "123456789012",
"region": "us-east-2",
"detail": {},
"detail-type": "Scheduled Event",
"source": "aws.events",
"time": "2019-03-01T01:23:45Z",
"id": "cdc73f9d-aea9-11e3-9d5a-835b769c0d9c",
"resources": [
"arn:aws:events:us-east-2:123456789012:rule/my-schedule"
]
}

It's good enough if you want to integrate with just one cron job, but what if you want more?

One option is to check the resources property, but I don't like that solution, so I'll introduce it to you in a way.

When selecting the target as AWS Lambda, you can configure in Additional Settings the input target as Input Transformer, with this option you can modify the above JSON into something different or add new properties.

After clicking Configure Input Transformer, you can choose the Scheduled Event in the sample event to get an idea of what the event will look like after the transformation.

In the Input Path inside the Target Input Transformer you will put this json:

input-path.json
{
"account": "$.account",
"detail-type": "$.detail-type",
"id": "$.id",
"region": "$.region",
"resources": "$.resources",
"source": "$.source",
"time": "$.time",
"version": "$.version"
}

And inside Template, you will put this json:

{
"version": "<version>",
"id": "<id>",
"detail-type": "<detail-type>",
"source": "<source>",
"account": "<account>",
"time": "<time>",
"region": "<region>",
"resources": [],
"detail": {
"action": "my_25min_cron"
}
}

Did you see the action inside the details? This property will be added when the event comes from Schedule Expression, with this property you can differentiate which cron was called.

Inside your controller, you can write code like this:

event-bridge.controller.ts
import type { ScheduledEvent } from 'aws-lambda';

// inside your controller method
type ScheduleEventWithAction = ScheduledEvent<{ action: 'my_25min_cron' | 'my_50min_cron' }>;
const scheduleEvent = request.body as unknown as ScheduleEventWithAction;

switch(scheduleEvent.detail.action) {
case 'my_25min_cron':
console.log('Your 25 min schedule expression was called.');
break;
case 'my_50min_cron':
console.log('Your 50 min schedule expression was called.');
break;
default:
console.error('The action was not recognized.');
break;
}

Customizing

You can change the HTTP Method and Path that will be used to create the request by sending eventBridgeForwardMethod and eventBridgeForwardPath inside EventBridgeOptions.

Usage

To add support to AWS EventBridge you do the following:

index.ts
import { ServerlessAdapter } from '@h4ad/serverless-adapter';
import { EventBridgeAdapter } from '@h4ad/serverless-adapter/adapters/aws';
import { DefaultHandler } from '@h4ad/serverless-adapter/handlers/default';
import app from './app';

export const handler = ServerlessAdapter.new(app)
.setHandler(new DefaultHandler())
// .setFramework(new ExpressFramework())
// .setResolver(new PromiseResolver())
.addAdapter(new EventBridgeAdapter())
// customizing:
// .addAdapter(new EventBridgeAdapter({ eventBridgeForwardPath: '/prod/eventbridge', eventBridgeForwardMethod: 'PUT' }))
.build();
caution

When you configure your API with some basePath like /prod, you should set eventBridgeForwardPath as /prod/eventbridge instead leave as default /eventbridge.

Security

You MUST check if the header Host contains the value of events.amazonaws.com.

Without checking this header, if you add this adapter and AWS API Gateway V2 adapter, you will be vulnerable to attacks because anyone can create a POST request to /eventbridge.

What happens when my response status is different from 2xx or 3xx?

Well, this library will throw an error. In previous versions of this library, the behavior was different, but now we throw an error if the status does not indicate success.

When it throws an error, the request will simply fail to process the event, and depending on how you set up your dead-letter queue or your retry police, can be sent to dead-letter queue for you to check what happens or try again.