Quick start
A Lambda handling an SQS queue takes two files. Once that works, adding a second event source to the same Lambda is one more router, which is the part worth being here for.
Install
One package per event source your Lambda receives, plus base. base exports LambdaRouter, which every router plugs into. It is a peer dependency, so you install it yourself.
npm install @lambda-event-router/base @lambda-event-router/sqsA Lambda sitting behind an HTTP API and a queue adds one more:
npm install @lambda-event-router/base @lambda-event-router/apigateway @lambda-event-router/sqsIf you are not sure which package you need, the packages page lists every AWS event source we cover.
Your first Lambda
1. Create a router and register a route
import { createSQSRouter } from '@lambda-event-router/sqs'
import { z } from 'zod'
import { orders } from '../services/orders.js'
const ORDER_QUEUE_ARN = 'arn:aws:sqs:eu-west-2:123456789012:orders'
const OrderSchema = z.object({
orderId: z.string(),
quantity: z.number().int().positive(),
})
export const sqsRouter = createSQSRouter({ batchItemFailures: true })
sqsRouter.route({
filters: {
eventSourceArn: ORDER_QUEUE_ARN,
messageAttributes: { type: 'OrderPlaced' },
},
bodySchema: OrderSchema,
handler: async ({ body }) => {
// body is typed from OrderSchema, with nothing to declare
await orders.save({ orderId: body.orderId, quantity: body.quantity })
},
})Breaking that down:
createSQSRoutermakes a router for one event source.batchItemFailures: truereports failing records individually so only those get redeliveredfilterssays which records this route takes, and every key has to match. This one only takesOrderPlacedmessages off that queue, so a second route can filter onOrderCancelledand get its own handler. The keys come from the event source, and routing lists thembodySchemaparses the message JSON and validates it, then typesbodyfrom the same schema. See schema validation for which libraries work and what a failure does- SQS hands Lambda a batch of records. The router unpacks it and calls your handler once per record, so ten messages arriving together run it ten times and you write for one message rather than a loop. Handlers covers everything else the request carries
2. Export the Lambda handler
import type { Handler } from 'aws-lambda'
import { LambdaRouter } from '@lambda-event-router/base'
import { sqsRouter } from './routers/sqs.js'
const lambdaRouter = new LambdaRouter({ routers: [sqsRouter] })
export const handler: Handler = lambdaRouter.handler()LambdaRouter is what AWS invokes. It works out which of its routers owns each event before any of your filters run, so nothing in your code has to sniff at the event shape.
Congratulations, that is a working Lambda. Point the queue at it and every message reaches your handler already parsed, validated and typed.
Multiple event sources
One LambdaRouter takes as many routers as you need, and each event reaches exactly one of them. Matching happens twice: LambdaRouter picks the router from the shape of the event, then that router picks the route from your filters.
- A
PUT /items/:itemIdrequest goes toapiRouter, then to the route filtering on that method and path - A stream record with
eventName: 'INSERT'goes todynamoRouter, then to the route filtering on that event name and stream ARN
Adding the second router cannot change how the first one's events are routed, so the API behaves exactly as it did before the stream existed.
Open a file: api-handlers/putItem.ts | db-handlers/onItemInserted.ts | api.ts | dynamodb.ts | index.ts
Both handlers above are plain functions with their request types named. Those types can be inferred from the route's filters and schemas instead when using defineRoute, which puts the route definition in the handler's file rather than the router's. See handlers for the same two handlers written that way.
ApiRequest takes the path params, then the query params, then the body, and this route sets no querySchema so its query is unknown. Returning the item on its own gives a 200 with the item as the body, and the response helpers cover the other status codes.
Where next
| Page | What it covers |
|---|---|
| Routers | What a router is, and what every one of them has in common |
| Routing | Filter keys, custom, match order and where schemas go |
| Handlers | What a handler is given and what it may return |
| Middleware | Running code around your handlers |
| Packages | Every router, with a page each |