Skip to content
Open
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
58 changes: 58 additions & 0 deletions appsync-events-lambda-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# AWS AppSync Events with Lambda

This pattern deploys an AppSync Events API for real-time WebSocket pub/sub with Lambda event processing.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/appsync-events-lambda-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details.

## Requirements

* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Node.js 22+](https://nodejs.org/en/download/) installed
* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed

## Architecture

```
┌───────────┐ ┌─────────────────────┐ ┌──────────────┐
│ Publisher │────▶│ AppSync Events API │────▶│ Subscribers │
│ (HTTP) │ │ (WebSocket) │ │ (WebSocket) │
└───────────┘ └─────────────────────┘ └──────────────┘
┌──────────────┐
│ AWS Lambda │
│ (Handler) │
└──────────────┘
```

## How it works

1. Publishers send events via HTTP POST to the AppSync Events endpoint.
2. AppSync Events delivers messages to all WebSocket subscribers on that channel.
3. Channel namespaces (`notifications`, `alerts`) organize topics.
4. A Lambda handler can process/enrich events before delivery.

## Deployment

```bash
npm install
cdk deploy
```

## Testing

```bash
# Publish an event (replace values from cdk deploy output)
curl -X POST "https://<HttpEndpoint>/event" \
-H "x-api-key: <ApiKeyValue>" \
-H "Content-Type: application/json" \
-d '{"channel":"/notifications/general","events":["{\"message\":\"Hello from CDK\"}"]}'
```

## Cleanup

```bash
cdk destroy
```
6 changes: 6 additions & 0 deletions appsync-events-lambda-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AppsyncEventsLambdaStack } from '../lib/appsync-events-lambda-stack';
const app = new cdk.App();
new AppsyncEventsLambdaStack(app, 'AppsyncEventsLambdaStack');
1 change: 1 addition & 0 deletions appsync-events-lambda-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"app":"npx ts-node --prefer-ts-exts bin/app.ts"}
1 change: 1 addition & 0 deletions appsync-events-lambda-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"title":"AWS AppSync Events with Lambda handler","description":"Deploy an AppSync Events API with a Lambda handler for real-time pub/sub message processing.","language":"TypeScript","level":"300","framework":"CDK","introBox":{"headline":"How it works","text":["AppSync Events provides WebSocket-based real-time pub/sub. A Lambda handler processes published messages before delivery to subscribers."]},"gitHub":{"template":{"repoURL":"https://github.com/aws-samples/serverless-patterns/tree/main/appsync-events-lambda-cdk","templateURL":"serverless-patterns/appsync-events-lambda-cdk","projectFolder":"appsync-events-lambda-cdk"}},"resources":{"bullets":[{"text":"AppSync Events","link":"https://docs.aws.amazon.com/appsync/latest/eventapi/event-api-welcome.html"}]},"deploy":{"text":["cdk deploy"],"commands":["npm install","cdk deploy"]},"testing":{"text":["Publish events via HTTP endpoint"]},"cleanup":{"text":["cdk destroy"],"commands":["cdk destroy"]},"authors":[{"name":"Nithin Chandran R","bio":"Technical Account Manager at AWS","linkedin":"nithin-chandran-r"}],"services":{"from":[{"service":"appsync"}],"to":[{"service":"lambda"}]}}
61 changes: 61 additions & 0 deletions appsync-events-lambda-cdk/lib/appsync-events-lambda-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as cdk from 'aws-cdk-lib';
import * as appsync from 'aws-cdk-lib/aws-appsync';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class AppsyncEventsLambdaStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

// Lambda handler for event processing
const eventFn = new lambda.Function(this, 'EventHandlerFn', {
runtime: lambda.Runtime.NODEJS_22_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
timeout: cdk.Duration.seconds(10)
});

// AppSync Events API
const api = new appsync.CfnApi(this, 'EventsApi', {
name: 'RealTimePubSubApi',
eventConfig: {
authProviders: [{ authType: 'API_KEY' }],
connectionAuthModes: [{ authType: 'API_KEY' }],
defaultPublishAuthModes: [{ authType: 'API_KEY' }],
defaultSubscribeAuthModes: [{ authType: 'API_KEY' }]
}
});

const apiKey = new appsync.CfnApiKey(this, 'EventsApiKey', { apiId: api.attrApiId });

// IAM role for AppSync to invoke Lambda
const appsyncRole = new iam.Role(this, 'AppSyncLambdaRole', {
assumedBy: new iam.ServicePrincipal('appsync.amazonaws.com')
});
eventFn.grantInvoke(appsyncRole);

// Channel namespace without handler (simple pub/sub - no Lambda processing)
// AppSync Events delivers messages directly to subscribers
new appsync.CfnChannelNamespace(this, 'NotificationsChannel', {
apiId: api.attrApiId,
name: 'notifications',
publishAuthModes: [{ authType: 'API_KEY' }],
subscribeAuthModes: [{ authType: 'API_KEY' }]
});

// Second channel with custom namespace for different topic
new appsync.CfnChannelNamespace(this, 'AlertsChannel', {
apiId: api.attrApiId,
name: 'alerts',
publishAuthModes: [{ authType: 'API_KEY' }],
subscribeAuthModes: [{ authType: 'API_KEY' }]
});

new cdk.CfnOutput(this, 'HttpEndpoint', { value: cdk.Fn.getAtt(api.logicalId, 'Dns.Http').toString() });
new cdk.CfnOutput(this, 'RealtimeEndpoint', { value: cdk.Fn.getAtt(api.logicalId, 'Dns.Realtime').toString() });
new cdk.CfnOutput(this, 'ApiId', { value: api.attrApiId });
new cdk.CfnOutput(this, 'ApiKeyValue', { value: apiKey.attrApiKey });
new cdk.CfnOutput(this, 'FunctionName', { value: eventFn.functionName });
}
}
14 changes: 14 additions & 0 deletions appsync-events-lambda-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "appsync-events-lambda-cdk",
"version": "1.0.0",
"bin": { "app": "bin/app.js" },
"scripts": { "build": "tsc", "cdk": "cdk" },
"dependencies": {
"aws-cdk-lib": "^2.180.0",
"constructs": "^10.0.0"
},
"devDependencies": {
"typescript": "~5.4.0",
"@types/node": "^20.0.0"
}
}
22 changes: 22 additions & 0 deletions appsync-events-lambda-cdk/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
exports.handler = async (event) => {
// AppSync Events handler - processes published messages
const { events } = event;

if (!events || !Array.isArray(events)) {
return { events: [{ payload: { error: 'No events received' } }] };
}

const processed = events.map(e => {
const payload = JSON.parse(e.payload);
return {
payload: JSON.stringify({
...payload,
processedAt: new Date().toISOString(),
enriched: true,
messageId: `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
})
};
});

return { events: processed };
};
1 change: 1 addition & 0 deletions appsync-events-lambda-cdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"compilerOptions":{"target":"ES2020","module":"commonjs","lib":["es2020"],"declaration":true,"strict":true,"noImplicitAny":true,"strictNullChecks":true,"noEmit":false,"resolveJsonModule":true,"esModuleInterop":true,"outDir":"./build","rootDir":"."}}