Pattern: EventBridge Event-Driven Pipeline
Decouple API request acceptance from async processing using
emitEventto publish to EventBridge, with SQS as a buffered target for Lambda consumers.
The Pattern
// src/lambdas/api/feedly/webhook.post.ts
import { emitEvent } from '@mantleframework/core'
import { defineApiHandler, z } from '@mantleframework/validation'
import type { DownloadRequestedDetail } from '#types/events'
const FeedlyWebhookRequestSchema = z.object({ articleURL: z.string() })
const api = defineApiHandler({
auth: 'authorizer',
schema: FeedlyWebhookRequestSchema,
operationName: 'WebhookFeedly',
})
export const handler = api(async ({ context, userId, body, metadata }) => {
// Publish event — EventBridge routes to DownloadQueue → StartFileUpload Lambda
const eventDetail: DownloadRequestedDetail = {
fileId,
userId,
sourceUrl: body.articleURL,
correlationId: metadata.correlationId,
requestedAt: new Date().toISOString(),
}
await emitEvent({ detailType: 'DownloadRequested', detail: eventDetail })
return buildValidatedResponse(context, 202, { status: 'Accepted' }, ResponseSchema)
})// src/lambdas/sqs/StartFileUpload/index.ts — the downstream consumer
import { emitEvent, defineSqsHandler } from '@mantleframework/core'
import type { DownloadCompletedDetail } from '#types/events'
const sqs = defineSqsHandler({
operationName: 'StartFileUpload',
parseBody: true,
timeout: 900,
memorySize: 2048,
queue: 'DownloadQueue',
})
export const handler = sqs(async (record) => {
// ... process download ...
// Publish completion event for downstream observability
const completedDetail: DownloadCompletedDetail = {
fileId, correlationId, s3Key: fileName,
fileSize: uploadResult.fileSize,
completedAt: new Date().toISOString(),
}
await emitEvent({ detailType: 'DownloadCompleted', detail: completedDetail })
})How It Works
The API Lambda accepts requests synchronously and immediately publishes a DownloadRequested event via emitEvent. EventBridge routes the event to an SQS queue using an inputTransformer (defined in mantle.config.ts) that reshapes the event detail into the queue message format. The SQS-triggered Lambda then processes the download asynchronously, publishing further events (DownloadCompleted, DownloadFailed) for observability. The API returns 202 Accepted without waiting for the download — decoupling request acceptance from potentially slow video processing.
Real-World Usage
- Publisher:
src/lambdas/api/feedly/webhook.post.ts - Consumer:
src/lambdas/sqs/StartFileUpload/index.ts
Configuration
// mantle.config.ts
export default defineConfig({
name: 'media-downloader',
eventbridge: {
bus: 'MediaDownloader',
sqsTargets: [
{
detailType: 'DownloadRequested',
queue: 'DownloadQueue',
inputTransformer: {
inputPaths: {
fileId: '$.detail.fileId',
sourceUrl: '$.detail.sourceUrl',
correlationId: '$.detail.correlationId',
userId: '$.detail.userId',
},
inputTemplate: `{
"fileId": <fileId>,
"sourceUrl": <sourceUrl>,
"correlationId": <correlationId>,
"userId": <userId>,
"attempt": 1
}`,
},
},
],
},
})The sqsTargets array tells Mantle's infra generator to create an EventBridge rule that matches detailType: 'DownloadRequested' and forwards messages to the named SQS queue, applying the inputTransformer to reshape the event payload.
Variations
- Direct Lambda target: Omit
sqsTargetsand usedefineEventBridgeHandleron the consumer Lambda — EventBridge invokes it directly without buffering. Use when you don't need SQS retry/visibility-timeout semantics. - Fan-out: Add multiple
sqsTargetsfor the samedetailTypeto notify several consumers from one event. - Chained events: Each processing stage emits its own event (
DownloadCompleted,DownloadFailed) so downstream services can react without polling.