In Part 1, we learned what WebSocket APIs are and why they’re awesome for real-time apps.
In Part 2, we’ll deploy our WebSocket API using separate CloudFormation templates for IAM, Lambda, and API Gateway — and then test it in Postman. 🚀
🧠 Why Modular Templates?
Splitting CloudFormation into multiple templates:
- Keeps code clean and manageable
- Lets teams work on different stacks independently
- Makes redeployment faster
- Improves reusability for future projects
1️⃣ IAM Role Template – iam.yaml
AWSTemplateFormatVersion: 2010-09-09 Description: IAM Role for WebSocket Lambdas Parameters: LambdaRoleName: Type: String Default: WebSocketLambdaExecutionRole Description: Lambda role Resources: LambdaExecutionRole: Type: AWS::IAM::Role Properties: RoleName: !Ref LambdaRoleName AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: LambdaLogsPolicys PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "*" Tags: - Key: Project Value: WebSocketAPI - Key: Owner Value: Utkarsh Outputs: LambdaExecutionRoleArn: Description: ARN of the Lambda Execution Role Value: !GetAtt LambdaExecutionRole.Arn Export: Name: LambdaExecutionRoleArn
2️⃣ Lambda Functions Template – lambda.yaml
AWSTemplateFormatVersion: 2010-09-09 Description: Lambda functions for WebSocket API with Tags and CloudWatch Log Groups Parameters: LambdaRoleName: Type: String Default: WebSocketLambdaExecutionRole Description: ARN of IAM Role for Lambdas Resources: ######################## # LAMBDA FUNCTIONS ######################## OnConnectFunction: Type: AWS::Lambda::Function Properties: FunctionName: OnConnectFunction Handler: index.lambda_handler Role: !Sub arn:aws:iam::${AWS::AccountId}:role/${LambdaRoleName} Runtime: python3.12 Code: ZipFile: | def lambda_handler(event, context): print("Client connected:", event["requestContext"]["connectionId"]) return {"statusCode": 200} Tags: - Key: Project Value: WebSocketAPI - Key: Owner Value: Utkarsh OnDisconnectFunction: Type: AWS::Lambda::Function Properties: FunctionName: OnDisconnectFunction Handler: index.lambda_handler Role: !Sub arn:aws:iam::${AWS::AccountId}:role/${LambdaRoleName} Runtime: python3.12 Code: ZipFile: | def lambda_handler(event, context): print("Client disconnected:", event["requestContext"]["connectionId"]) return {"statusCode": 200} Tags: - Key: Project Value: WebSocketAPI - Key: Owner Value: Utkarsh SendMessageFunction: Type: AWS::Lambda::Function Properties: FunctionName: SendMessageFunction Handler: index.lambda_handler Role: !Sub arn:aws:iam::${AWS::AccountId}:role/${LambdaRoleName} Runtime: python3.12 Code: ZipFile: | import json def lambda_handler(event, context): body = json.loads(event["body"]) print("Message received:", body) return {"statusCode": 200} Tags: - Key: Project Value: WebSocketAPI - Key: Owner Value: Utkarsh ######################## # CLOUDWATCH LOG GROUPS ######################## OnConnectLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${OnConnectFunction} RetentionInDays: 14 Tags: - Key: Project Value: WebSocketAPI - Key: Owner Value: Utkarsh OnDisconnectLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${OnDisconnectFunction} RetentionInDays: 14 Tags: - Key: Project Value: WebSocketAPI - Key: Owner Value: Utkarsh SendMessageLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${SendMessageFunction} RetentionInDays: 14 Tags: - Key: Project Value: WebSocketAPI - Key: Owner Value: Utkarsh Outputs: OnConnectFunctionArn: Value: !GetAtt OnConnectFunction.Arn Export: Name: OnConnectFunctionArn OnDisconnectFunctionArn: Value: !GetAtt OnDisconnectFunction.Arn Export: Name: OnDisconnectFunctionArn SendMessageFunctionArn: Value: !GetAtt SendMessageFunction.Arn Export: Name: SendMessageFunctionArn
3️⃣ API Gateway WebSocket Template – apigateway.yaml
AWSTemplateFormatVersion: 2010-09-09 Description: API Gateway WebSocket API with Lambda integrations, Tags, and CloudWatch logging Parameters: OnConnectFunctionName: Type: String Default: OnConnectFunction Description: OnConnect Lambda OnDisconnectFunctionName: Type: String Default: OnDisconnectFunction Description: OnDisconnect Lambda SendMessageFunctionName: Type: String Default: SendMessageFunction Description: SendMessage Lambda Resources: ######################## # API GATEWAY - WEBSOCKET API ######################## ChatWebSocketAPI: Type: AWS::ApiGatewayV2::Api Properties: Name: ChatWebSocketAPI ProtocolType: WEBSOCKET RouteSelectionExpression: "$request.body.action" Tags: Project: WebSocketAPI Owner: Utkarsh ######################## # INTEGRATIONS ######################## ConnectIntegration: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ChatWebSocketAPI IntegrationType: AWS_PROXY IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${OnConnectFunctionName}/invocations DisconnectIntegration: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ChatWebSocketAPI IntegrationType: AWS_PROXY IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${OnDisconnectFunctionName}/invocations SendMessageIntegration: Type: AWS::ApiGatewayV2::Integration Properties: ApiId: !Ref ChatWebSocketAPI IntegrationType: AWS_PROXY IntegrationUri: !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${SendMessageFunctionName}/invocations ######################## # ROUTES ######################## ConnectRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ChatWebSocketAPI RouteKey: $connect AuthorizationType: NONE Target: !Join ["/", ["integrations", !Ref ConnectIntegration]] DisconnectRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ChatWebSocketAPI RouteKey: $disconnect AuthorizationType: NONE Target: !Join ["/", ["integrations", !Ref DisconnectIntegration]] SendMessageRoute: Type: AWS::ApiGatewayV2::Route Properties: ApiId: !Ref ChatWebSocketAPI RouteKey: sendmessage AuthorizationType: NONE Target: !Join ["/", ["integrations", !Ref SendMessageIntegration]] ######################## # PERMISSIONS ######################## ConnectLambdaPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${OnConnectFunctionName} Principal: apigateway.amazonaws.com DisconnectLambdaPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${OnDisconnectFunctionName} Principal: apigateway.amazonaws.com SendMessageLambdaPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${SendMessageFunctionName} Principal: apigateway.amazonaws.com ######################## # DEPLOYMENT + STAGE ######################## WebSocketDeployment: Type: AWS::ApiGatewayV2::Deployment Properties: ApiId: !Ref ChatWebSocketAPI DependsOn: - ConnectRoute - DisconnectRoute - SendMessageRoute DevStage: Type: AWS::ApiGatewayV2::Stage Properties: StageName: dev ApiId: !Ref ChatWebSocketAPI DeploymentId: !Ref WebSocketDeployment AccessLogSettings: DestinationArn: !GetAtt ApiGatewayLogGroup.Arn Format: '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","routeKey":"$context.routeKey","status":"$context.status","connectionId":"$context.connectionId"}' Tags: Project: WebSocketAPI Owner: Utkarsh ######################## # API GATEWAY LOG GROUP ######################## ApiGatewayLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/apigateway/${ChatWebSocketAPI}-dev RetentionInDays: 14 Tags: - Key: Project Value: WebSocketAPI - Key: Owner Value: Utkarsh Outputs: WebSocketURL: Description: WebSocket connection URL Value: !Sub wss://${ChatWebSocketAPI}.execute-api.${AWS::Region}.amazonaws.com/dev
🚀 Deploy in 3 Steps
Note: Make sure you have the AWS CLI installed and configured with appropriate credentials (
aws configure
) before running these commands.
Step 1: Deploy IAM Role
aws cloudformation deploy --template-file iam.yaml --stack-name WebSocket-IAM-Stack --capabilities CAPABILITY_NAMED_IAM
Step 2: Deploy Lambda Functions
aws cloudformation deploy --template-file lambda.yaml --stack-name WebSocket-Lambda-Stack --capabilities CAPABILITY_NAMED_IAM
Step 3: Deploy API Gateway WebSocket API
aws cloudformation deploy --template-file apigateway.yaml --stack-name WebSocket-APIGateway-Stack --capabilities CAPABILITY_NAMED_IAM
🧪 Test with Postman
Once the deployment is complete, you can test your WebSocket API using Postman.
1️⃣ Connect to WebSocket API
- Open Postman and create a new request.
- Change the request type to WebSocket (dropdown to the left of the URL bar).
-
In the URL field, paste your WebSocket endpoint from the CloudFormation output:
wss://<API_ID>.execute-api.<REGION>.amazonaws.com/dev
Click Connect
✅ This triggers the OnConnectFunction Lambda. Check CloudWatch Logs for a Client connected
message.
2️⃣ Send a Message
- In the WebSocket message box, paste:
{ "action": "sendmessage", "message": "Welcome To WebSocket Learning!" }
2.Click Send
✅ This triggers the SendMessageFunction Lambda. Check CloudWatch Logs.
3️⃣ Disconnect
- In Postman, click Disconnect in the WebSocket tab.
- ✅ This triggers the OnDisconnectFunction Lambda.
- Check CloudWatch Logs
📜 Summary
In this part, we:
- Deployed an AWS WebSocket API using three separate CloudFormation templates:
- IAM Role (
iam.yaml
) - Lambda Functions (
lambda.yaml
) - API Gateway WebSocket (
apigateway.yaml
)
- IAM Role (
- Connected to the WebSocket API using Postman
- Triggered and verified:
-
$connect
→ OnConnectFunction -
sendmessage
→ SendMessageFunction -
$disconnect
→ OnDisconnectFunction
-
- Viewed CloudWatch Logs to confirm each Lambda executed successfully
With this modular approach, you now have a fully functional real-time WebSocket API ready to extend.
💡 Coming in Part 3:
We’ll integrate DynamoDB to store chat messages and update the SendMessageFunction
to broadcast messages to all connected clients for a complete real-time chat experience.
👨💻 About Me
Hi! I'm Utkarsh, a Cloud Specialist & AWS Community Builder who loves turning complex AWS topics into fun chai-time stories ☕
Top comments (0)