Skip to content

Commit e098acf

Browse files
authored
Merge pull request #2773 from humem001/mjhume-appsync-ws-lambda-bedrock-sam
New serverless pattern - appsync websockets with lambda and bedrock
2 parents 1adef7b + 6fff67d commit e098acf

File tree

17 files changed

+1128
-0
lines changed

17 files changed

+1128
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
.aws-sam/

apigw-ws-lambda-bedrock-sam/README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# AI Chat Application using Amazon API Gateway (WebSockets), AWS Lambda, and Amazon Bedrock.
2+
3+
This serverless architecture enables real-time AI chat using AWS services. A WebSocket Amazon API Gateway maintains persistent connections between clients and a Node.js AWS Lambda function. The AWS Lambda function handles user connections/disconnections, stores connection IDs in Amazon DynamoDB, and processes messages through an Amazon Bedrock LLM. The system includes error handling, automatic scaling, and pay-per-use pricing. The AWS SAM template provisions all necessary resources and IAM permissions, outputting a WebSocket URL for client connections.
4+
5+
6+
Learn more about this pattern at [Serverless Land Patterns](https://serverlessland.com/patterns/apigw-websockets-lambda-bedrock-sam)
7+
8+
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. You are responsible for any AWS costs incurred. No warranty is implied in this example.
9+
10+
## Prerequisites
11+
12+
* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
13+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
14+
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
15+
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed
16+
* [NOTE! Manage Access to Amazon Bedrock Foundation Models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html)
17+
18+
19+
## Deployment Instructions
20+
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
21+
```
22+
git clone https://github.com/aws-samples/serverless-patterns
23+
```
24+
2. Change directory to the pattern directory:
25+
```
26+
cd apigw-websockets-lambda-bedrock-sam
27+
```
28+
3. Install dependencies
29+
```
30+
cd src && npm install && cd ..
31+
```
32+
4. From the command line, use AWS SAM build to prepare an application for subsequent steps in the developer workflow, such as local testing or deploying to the AWS Cloud:
33+
```
34+
sam build
35+
```
36+
5. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
37+
```
38+
sam deploy --guided
39+
```
40+
6. During the prompts:
41+
* Enter a stack name
42+
* Enter the desired AWS Region
43+
* Enter the desired BedrockRegion
44+
* Enter the desired BedrockModelId
45+
* Allow SAM to create roles with the required permissions if needed.
46+
47+
7. Note the outputs **WebSocket URL** from the SAM deployment process.
48+
These contain the resource names and/or ARNs which are used for testing.
49+
```bash
50+
wss://<YOUR-WebSocket-URL>.execute-api.<YOUR-AWS-Region>.amazonaws.com/prod
51+
```
52+
53+
## Architecture
54+
![apigw-1](images/apigw-ws-lambda-bedrock.png)
55+
56+
57+
## How it Works
58+
WebSocket API Gateway serves as the entry point, enabling bidirectional real-time communication between clients and the backend. It handles three routes:
59+
* $connect - when users join the chat
60+
* $disconnect - when users leave
61+
* $default - for processing chat messages
62+
63+
Lambda Function (Node.js 22.x) acts as the central orchestrator, handling all WebSocket events and business logic. It manages connection lifecycle, processes user messages, and coordinates with other AWS services.
64+
DynamoDB Table stores active WebSocket connection IDs, enabling the system to track which users are currently connected and send responses back to the correct clients.
65+
Amazon Bedrock provides AI capabilities using Claude 3 Sonnet model, processing user messages and generating intelligent responses.
66+
67+
Data Flow:
68+
1. Connection: When a user connects, their connection ID is stored in DynamoDB
69+
2. Message Processing: User sends a message through WebSocket → Lambda receives it → Extracts message content → Sends to Bedrock Claude model
70+
3. AI Response: Bedrock processes the message and returns an AI-generated response
71+
4. Real-time Delivery: Lambda sends the AI response back to the user via WebSocket connection
72+
5. Disconnection: When user disconnects, their connection ID is removed from DynamoDB
73+
74+
## Testing
75+
76+
### Interactive Web Interface
77+
```
78+
To use the test interface:
79+
1. Deploy the application using SAM
80+
2. Copy the WebSocket URL from the deployment outputs
81+
3. Open 'test.html' and update the 'WS_URL' variable with your WebSocket URL and AWS Region
82+
wss://<YOUR-WebSocket-URL>.execute-api.<YOUR-AWS-Region>.amazonaws.com/prod
83+
4. Save 'test.html'
84+
5. Open the HTML file in a browser
85+
6. Click "Connect" to establish a WebSocket connection
86+
7. Type your message and click "Send"
87+
8. When complete Click "Disconnect"
88+
```
89+
90+
### Command Line (wscat)
91+
```bash
92+
npm install -g wscat
93+
wscat -c wss://your-api-id.execute-api.your-region.amazonaws.com/prod
94+
# Then send: {"data": "What is AWS Lambda?"}
95+
```
96+
97+
## Cleanup
98+
1. Delete the stack
99+
```bash
100+
sam delete
101+
```
102+
2. Confirm the stack has been deleted
103+
```bash
104+
aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus"
105+
```
106+
----
107+
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
108+
109+
SPDX-License-Identifier: MIT-0
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"title": "AI Chat with Amazon API Gateway (WebSockets), AWS Lambda, and Amazon Bedrock.",
3+
4+
"description": "WebSocket-Enabled AI Chat Using AWS Services.",
5+
6+
"language": "",
7+
"level": "200",
8+
"framework": "SAM",
9+
"introBox": {
10+
"headline": "How it works",
11+
"text": [
12+
"This serverless architecture enables real-time AI chat using AWS services. A WebSocket Amazon API Gateway maintains persistent connections between clients and a Node.js AWS Lambda function.",
13+
"The AWS Lambda function handles user connections/disconnections, stores connection IDs in Amazon DynamoDB, and processes messages through an Amazon Bedrock LLM.",
14+
"The system includes error handling, automatic scaling, and pay-per-use pricing.",
15+
"The AWS SAM template provisions all necessary resources and IAM permissions, outputting a WebSocket URL for client connections."
16+
]
17+
},
18+
"gitHub": {
19+
"template": {
20+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-websockets-lambda-bedrock-sam",
21+
"templateURL": "serverless-patterns/apigw-websockets-lambda-bedrock-sam",
22+
"projectFolder": "apigw-websockets-lambda-bedrock-sam",
23+
"templateFile": "template.yml"
24+
}
25+
},
26+
"resources": {
27+
"bullets": [
28+
{
29+
"text": "Amazon API Gateway",
30+
"link": "https://aws.amazon.com/api-gateway/"
31+
},
32+
{
33+
"text": "Amazon API Gateway WebSocket APIs",
34+
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html"
35+
},
36+
{
37+
"text": "AWS Lambda",
38+
"link": "https://aws.amazon.com/lambda/"
39+
},
40+
{
41+
"text": "Amazon Bedrock",
42+
"link": "https://aws.amazon.com/bedrock/"
43+
}
44+
]
45+
},
46+
"deploy": {
47+
"text": [
48+
"sam deploy"
49+
]
50+
},
51+
"testing": {
52+
"text": [
53+
"See the GitHub repo for detailed testing instructions."
54+
]
55+
},
56+
"cleanup": {
57+
"text": [
58+
"<code>sam delete</code>"
59+
]
60+
},
61+
"authors": [
62+
{
63+
"name": "Mike Hume",
64+
"image": "https://media.licdn.com/dms/image/D4E03AQEiUfmBiUOw_A/profile-displayphoto-shrink_200_200/0/1718324029612?e=1727308800&v=beta&t=ybhm76l-CP5xcUsHbdq2IaJOlfyycvQ6gNwuCSd3Z0w",
65+
"bio": "AWS Senior Solutions Architect & UKPS Serverless Lead.",
66+
"linkedin": "michael-hume-4663bb64",
67+
"twitter": ""
68+
}
69+
]
70+
}
106 KB
Loading
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2+
import { DynamoDBDocumentClient, PutCommand, DeleteCommand } from '@aws-sdk/lib-dynamodb';
3+
import { ApiGatewayManagementApiClient, PostToConnectionCommand } from '@aws-sdk/client-apigatewaymanagementapi';
4+
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime';
5+
6+
const ddbClient = new DynamoDBClient({});
7+
const ddb = DynamoDBDocumentClient.from(ddbClient);
8+
// Bedrock client using region from environment variable
9+
const bedrock = new BedrockRuntimeClient({ region: process.env.BEDROCK_REGION });
10+
11+
export const handler = async (event) => {
12+
const { routeKey, connectionId } = event.requestContext;
13+
const tableName = process.env.CONNECTIONS_TABLE;
14+
15+
try {
16+
switch (routeKey) {
17+
case '$connect':
18+
await ddb.send(new PutCommand({
19+
TableName: tableName,
20+
Item: { connectionId }
21+
}));
22+
return { statusCode: 200 };
23+
24+
case '$disconnect':
25+
await ddb.send(new DeleteCommand({
26+
TableName: tableName,
27+
Key: { connectionId }
28+
}));
29+
return { statusCode: 200 };
30+
31+
case '$default':
32+
const message = JSON.parse(event.body || '{}');
33+
const userMessage = message.data || 'Hello';
34+
35+
const apiGw = new ApiGatewayManagementApiClient({
36+
endpoint: `https://${event.requestContext.domainName}/${event.requestContext.stage}`
37+
});
38+
39+
try {
40+
// Invoke Bedrock Claude model
41+
const bedrockResponse = await bedrock.send(new InvokeModelCommand({
42+
modelId: process.env.BEDROCK_MODEL_ID,
43+
body: JSON.stringify({
44+
anthropic_version: 'bedrock-2023-05-31',
45+
max_tokens: 1000,
46+
messages: [{
47+
role: 'user',
48+
content: userMessage
49+
}]
50+
})
51+
}));
52+
53+
const responseBody = JSON.parse(new TextDecoder().decode(bedrockResponse.body));
54+
const aiResponse = responseBody.content[0].text;
55+
56+
await apiGw.send(new PostToConnectionCommand({
57+
ConnectionId: connectionId,
58+
Data: JSON.stringify({ message: aiResponse })
59+
}));
60+
} catch (bedrockError) {
61+
console.error('Bedrock error:', bedrockError);
62+
await apiGw.send(new PostToConnectionCommand({
63+
ConnectionId: connectionId,
64+
Data: JSON.stringify({
65+
message: `Sorry, I'm having trouble connecting to the AI service. Please try again in a moment.`
66+
})
67+
}));
68+
}
69+
70+
return { statusCode: 200 };
71+
72+
default:
73+
return { statusCode: 400, body: 'Unknown route' };
74+
}
75+
} catch (error) {
76+
console.error('Error:', error);
77+
return { statusCode: 500, body: JSON.stringify({ error: error.message }) };
78+
}
79+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "websocket-handler",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"dependencies": {
6+
"@aws-sdk/client-dynamodb": "^3.0.0",
7+
"@aws-sdk/lib-dynamodb": "^3.0.0",
8+
"@aws-sdk/client-apigatewaymanagementapi": "^3.0.0",
9+
"@aws-sdk/client-bedrock-runtime": "^3.0.0"
10+
}
11+
}

0 commit comments

Comments
 (0)