DEV Community

Cover image for AWS WebSocket Wonders – Part 2: Build & Test a WebSocket API

AWS WebSocket Wonders – Part 2: Build & Test a WebSocket API

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

🚀 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 
Enter fullscreen mode Exit fullscreen mode

Step 2: Deploy Lambda Functions

aws cloudformation deploy --template-file lambda.yaml --stack-name WebSocket-Lambda-Stack --capabilities CAPABILITY_NAMED_IAM 
Enter fullscreen mode Exit fullscreen mode

Step 3: Deploy API Gateway WebSocket API

aws cloudformation deploy --template-file apigateway.yaml --stack-name WebSocket-APIGateway-Stack --capabilities CAPABILITY_NAMED_IAM 
Enter fullscreen mode Exit fullscreen mode

🧪 Test with Postman

Once the deployment is complete, you can test your WebSocket API using Postman.


1️⃣ Connect to WebSocket API

  1. Open Postman and create a new request.
  2. Change the request type to WebSocket (dropdown to the left of the URL bar).
  3. In the URL field, paste your WebSocket endpoint from the CloudFormation output:

    wss://<API_ID>.execute-api.<REGION>.amazonaws.com/dev 
  4. Click Connect

Connect

✅ This triggers the OnConnectFunction Lambda. Check CloudWatch Logs for a Client connected message.


2️⃣ Send a Message

  1. In the WebSocket message box, paste:
{ "action": "sendmessage", "message": "Welcome To WebSocket Learning!" } 
Enter fullscreen mode Exit fullscreen mode

2.Click Send

Send

✅ This triggers the SendMessageFunction Lambda. Check CloudWatch Logs.


3️⃣ Disconnect

  1. In Postman, click Disconnect in the WebSocket tab.
  2. ✅ This triggers the OnDisconnectFunction Lambda.
  3. 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)
  • Connected to the WebSocket API using Postman
  • Triggered and verified:
    • $connectOnConnectFunction
    • sendmessageSendMessageFunction
    • $disconnectOnDisconnectFunction
  • 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

👉 Explore more


Top comments (0)